High Performance iOS Apps - Reading Note - Part III


With the necessity to use networking in an app and with limited choices for minimiz‐ ing latency (e.g., using CDNs or edge servers or using smaller payload formats like Protobuf or data compressions), everything gets down to best utilizing the available network conditions and planning ahead for varying scenarios.

Metrics and Measurement

DNS Lookup Time

To minimize the latencies that arise from DNS lookup times, follow these best practices:

  • Minimize the number of unique domains the app uses. Multiple domains are unavoidable because of the way routing works in general. Most likely, you will need one each for the following:

    1. Identity management (login, logout, profile)
    2. Data serving (API endpoints)
    3. CDN (images and other static artifacts)

      There may be a need for other domains (e.g., for serving video, uploading instrumentation data, subcomponent-specific data serving, serving ads, or even country-specific geolocalization). If the number of subdomains goes up to double digits, that can be cause for worry.

  • Connection to all domains may not be required at app startup. During app launch, identity management and data for the initial screen might be all that the app requires. For subsequent subdomains, try to have preemptive DNS resolu‐ tion, a.k.a. DNS prefetch. There are two options for accomplishing this.
    If you have subdomains and hosting under your control, you can configure a predetermined URL to return a HTTP 204 status code with no content and make an early connection to this URL.
    The second option is to use gethostbyname to perform an explicit DNS lookup. However, the host may resolve to a different IP for different protocols, such as one address for HTTP requests and another for HTTPS requests. Though not very common, Layer 7 routing can resolve the IP address based on the actual request—for example, one address for images and another for video. For these reasons, resolving DNS before connecting is often unhelpful and a dummy call to the host is more effective.

SSL Handshake Time

best practices:

  • Minimize the number of connections that the app makes. As a corollary to this, minimize the number of unique hostnames the app connects to.
  • Do not close HTTP/S connections after the request is complete.
    Add the header Connection: keep-alive for all HTTPS calls. This ensures that the same connection can be reused for the next request.
  • Use domain sharding. This allows you to use the same socket even if the connec‐ tions are for multiple hostnames, as long as they resolve to the same IP and the same certificates can be used (e.g., in wildcard domains).
    Domain sharding is available in SPDY or its successor, HTTP/2. You will need a networking library that supports either of the two formats.

iOS 9 has native support for HTTP/2.
For iOS 8 and earlier, you will need a third-party library such as CocoaSPDY

Network Type

best practices when developing network-centric apps:

  • Design for variable network availability. The only aspect that is consistent in mobile networks is variance in network availability. For media streaming, prefer HTTP Live Streaming (HLS) or any of the available adaptive bitrate streaming technologies, as these will allow dynamic switching across the best streaming quality for the available bandwidth at the moment, resulting in smooth video play.
    For non-streaming content, you will need to implement strategies on how much data should be downloaded in a single fetch—and this has to be adaptive. For example, you may not want to fetch all 200 new emails since the last update in one go. It may be prudent to start by downloading the first 50 emails and then progressively download more.
    Similarly, do not turn on the video autoplay on low-speed networks or those that may cost a lot of money for the user.
    For custom non-streaming data fetch, keep intelligence on the server. Let the cli‐ ent send network characteristics and the server decide the number of records to return. This will allow you to make adaptive changes without having to release a new version of the app.
  • In case of failures, retry a er a random and exponentially growing delay.
    For example, after the first failure, the app might retry after 1 second. On the sec‐ ond failure, it would then retry after 2 seconds, followed by a 4-second delay. Do not forget to have a maximum automatic retry count per session.
  • Establish a minimum time between forced refreshes. When the user asks for explicit refresh, do not fire off the request immediately. Instead, check if either a request is already pending or the time gap from the last attempt is less than a threshold. If so, do not send the request.
  • Use reachability to discover any changes to the network state. use indicators to show any unavailability to the user. After all, it is not your fault that the device does not have Internet access. By letting users know about potential connection issues, you will avoid blame being placed on your app.
  • Do not cache the network state. Always use the latest value for network-sensitive tasks, either through a callback to know when to trigger a request or an explicit check before a request is made.
  • Download content based on the network type. If you have an image to show, do not always download the original, high-quality image. Always download the image that is suitable for the device—the image size requirements for an iPhone 4S can be very different from those of an iPad 3rd Generation.
    If your app has video content, it is a good idea to have a preview image associated with it. If the app supports an autoplay feature, use the preview image to be shown on non-WiFi networks, as they are known to cost a lot of money to the user.
    In addition, include an option for turning off autodownload and/or autoplay of heavy content such as images, audio, and video.
  • Prefetch optimistically. When on a WiFi network, prefetch content that you think the user will need after some time. Use this cached content subsequently. Prefer downloading in bursts and let the network radio be shut down after use. This will help save battery.
  • If applicable, support o ine storage with sync when the network is available. More often than not, the network cache should suffice. But if you need more structured data, using local files or Core Data is always a preferred option.
    For a game, cache the last level-up details. For a mail app, storing a few of the latest emails with attachments is a great option.
    Depending on the app, you may allow users to create new content offline that can be synchronized with the server when network connectivity is available. Exam‐ ples include composing a new email or responding to one in a mail app, updating a profile photo in a social app, and capturing photos or videos to be uploaded later.
    Always decouple networking and communication from the user interface. If the app can perform operations offline, notify the user that it can. Otherwise, notify the user that it cannot. Do not let the user start an interaction with the app and then lead to a point of no return—this is a poor user experience.


Network latency can be measured by subtracting the time spent on the server (in computation and serving the response) from the total time spent during the request:
Round-Trip Time = (Timestamp of Response - Timestamp of Request)
Network Latency = Round-Trip Time - Time Spent on Server

The time spent on server can be computed by the server. The round-trip time is accu‐ rately available to the client. The server can send the time spent in a custom header in the response that can then be used on the client side to compute the latency.


//server - NodeJS
app.post("/some/path", function(req, res) { 
    var startTime = new Date().getTime(); //process
    var body = processRequest(req);
    var endTime = new Date().getTime();
    var serverTime = endTime - startTime; 
    res.header("X-    Server-Time", String(serverTime)); 

//client - iOS app
-(void)fireRequestWithLatency:(NSURLRequest *)request {
    NSDate *startTime = nil;
    AFHTTPRequestOperation *op =
    [[AFHTTPRequestOperation alloc] initWithRequest:request];
    [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *op, id res) {
        NSDate *endTime = [NSDate date];
        NSTimeInterval roundTrip = [endTime timeIntervalSinceDate:startTime];             long roundTripMillis = (long)(roundTrip * 1000);
        NSHTTPURLResponse *res = op.response;
        NSString *serverTime = [res.allHeaderFields objectForKey:@"X-Server-Time"];
        long serverTimeMillis = [serverTime longLongValue];
        long latencyMillis = roundTripMillis - serverTimeMillis;
    } failure:^(AFHTTPRequestOperation *op, NSError *error) { 
        //Process error. Present error to the user, if need be.
    startTime = [NSDate date];
    [op start]; 

While you have the data to analyze any patterns in latency, additionally keep track of the following data:

Connection timeouts

It’s important to keep track of how many times the connection timed out. This metric will provide you details of geographic distribution categorized by network quality—either poor infrastructure or lower capacity—which in turn will help you plan sync-time spread. For example, instead of syncing at a specific time across a time zone, the sync can be spread over a short duration of time, say, a few minutes.

Response timeouts

Capture the number of times the connection succeeded but the response timed out. This will help you plan datacenter capacity based on geographic location and times of the day and year.

Payload size

The request as well as the response size can be measured completely on the server side. Use this data to identify any peaks that can slow down your network operations and determine options for either reducing the total data footprint by selecting appropriate serialization format (JSON, CSV, Protobuf, etc.) or splitting the data and using incremental syncs (e.g., by using smaller batch sizes or send‐ ing partial data in multiple chunks).

Networking API

the advantages of using NSURLSession:

  • NSURLSession is a configurable container for putting related requests into. As an example, all calls to your servers can be configured to always include an access token.
  • You get all the benefits of background networking. This helps with battery life, supports UIKit multitasking, and uses the same delegate model as in-process transfers.
  • Any networking task can be paused, stopped, and restarted. Unlike with NSURL Connection, there is no need for NSOperation subclasses.
  • You can subclass NSURLSession to configure a session to use private storage (cache, cookie jar, etc.) on a per-session basis.
  • When using NSURLConnection, if an authentication challenge was issued, the challenge would come back for an arbitrary request and you wouldn’t knowexactly what request was getting the challenge. With NSURLSession, the delegate handles authentication.
  • NSURLConnection has some asynchronous block–based methods, but a delegate cannot be used with them. When a request is made, it either succeeds or fails, even if authentication was needed.
    With NSURLSession, you can take a hybrid approach—that is, you can use the asynchronous block–based methods and also set up a delegate to handle authen‐ tication.

App Deployment


  • Use multiple datacenters, so that your servers are spread out geographically, closer to your users.
  • Use CDNs to serve static content such as images, JavaScript, CSS, fonts, and so on.
  • Use edge servers in proximity to serve dynamic content.
  • Avoid multiple domains (DNS lookup times can be long and diminish the user

An edge server, in a system administration context, is any server that resides on the “edge” between two networks, typically a private network and the Internet. Edge servers can serve different purposes depending on the context of the functionality in question.

Some examples:

  • Security Context: usually a firewall, router or similar device
  • Application Context: a web load balancing server
  • Mail Context: some kind of hub server that forwards mail on to internal servers

Usually an edge server has some kind of gateway responsibility for the internal/private network.


  • Instead of making one request for each unit of operation, make batch requests. Even if you have to implement multiple backend subsystems to do so, consolidat‐ ing batches of requests provide enough of a performance gain that it’s usually worthwhile.
    The client can post a multiplexed request with data for multiple backends and the server can respond with a multipart/mixed response. The client will have to demultiplex the response.

  • Use persistent HTTP connections, also known as HTTP keep-alive. They help
    minimize TCP and SSL handshake overheads, and reduce network congestion.
    Alternatively, use WebSockets. Libraries like SocketRocket from Square can help you get started with using WebSockets on iOS.

  • Use HTTP/2 whenever available. HTTP/2 supports true multiplexing of HTTP requests over a single connection, coalescing requests across multiple sub- domains into one if they resolve to one IP address, header compression, and much more. The benefits of using HTTP/2 are enormous. And the best part is that the protocol remains unchanged as far as the message structure is con‐ cerned, which continues to be comprised of headers and body.

  • Use HTTP cache headers for the correct level of caching. For standard images that you intend to download (e.g., theme backgrounds or emoticons), the content can have an expiry date far ahead in the future. This ensures not only that the net‐ working library caches them locally, but also that other devices benefit from intermediary servers (ISP servers or proxies) caching them locally. The response headers that affect HTTP caching are Last-Modified, Expires, ETag, and Cache- Control.

Data Format

  • Use data compression. This is particularly important when transferring text con‐ tent such as JSON or XML. NSURLRequest automatically adds the header Accept- Encoding: gzip, deflate so that you do not have to do that yourself. But this also means that the server should acknowledge the header and send the data using the appropriate Transfer-Encoding.

  • Choose the correct data format. It is a no-brainer that verbose, human-readable formats such as JSON and XML are resource intensive—serialization, transport, and deserialization takes much longer than using a custom-crafted, binary, machine-friendly format. We will not discuss media compression (i.e., image compression and video codecs), but rather focus on text data formats.
    The most commonly chosen data formats for native apps happen to be JSON and XML. And the only reason is that the web services/APIs were written for the Web and repurposed for mobile.
    However, if you aren’t already, you need to start thinking mobile first. The previ‐ ously mentioned formats are handy to handcraft but resource intensive for machine operations. Prefer a more optimized format from both a size and a seri‐ alization/deserialization perspective.
    The most popular binary format to transport records is Protocol Buffers, a.k.a. Protobuf. Other protocols include Apache Thrift and Apache Avro. In general, Protobuf is known to outperform the others, but a lot can depend on the type of data being used. If the data is largely strings, you should find ways to optimize their loading, as they are not compressed by any of these formats. Compress the data using deflate, gzip, or any lossless compression algorithm.


The Network Link Conditioner allows you to simulate varying network conditions by con‐ trolling important parameters:

  • Inbound traffic. Bandwidth, packet loss, and delay (the response latency)
  • Outbound traffic. Bandwidth, packet loss, and delay
  • DNS. Lookup latency
  • Protocol. IPv4, IPv6, or both
  • Interface. WiFi, cellular, or both


  • Monitor HTTP requests
  • Monitor HTTPS requests
  • Send a custom response

Data Sharing

Deep Linking

There is a reserved list of URL schemes that an app cannot respond to:

  • http, https.
    Standard schemes for browsing the Web; handled by Safari. An exception is for YouTube links, which are opened by the YouTube app, if installed (this is due to a partnership with Google formed before Apple created its own video player).
  • mailto.
    Scheme to send emails; handled by the Mail app. Example: mailto:email@domain.com.
  • itms, itms-apps.
    Used to take a user to an app install screen; handled by the App Store application. These were the only options available until Store Kit was introduced in iOS 6.
  • tel.
    Used to call a phone number; handled by the Phone app. Example: tel:// 1234567890.
  • _app-settings.
    New in iOS 8, this scheme takes you to the Settings app and directly into the app settings.
  1. Detect if the scheme can be handled. The -[UIApplication canOpenURL:] method allows you to check if a specific scheme can be handled by at least one of the apps installed on the device. Choosing a unique scheme can help you detect if a specific app is installed or not.
  2. Open the URL into the app. Once the app’s presence has been detected, the next step is to create the final URL and open the app, using the -[UIApplication openURL] method to launch it.
  3. Handle links in the target app. When the app receives the URL, the UIAppDele gate gets a notification via the callback method -[UIAppDelegate applica tion:openURL:sourceApplication:annotation:]. Parse the incoming URL, extract the parameters/values, process, and proceed.

Best practices:

  • Prefer shorter URLs, as they are faster to construct and faster to parse.
  • Avoid regular expression–based patterns.
    If you use the Button Deep Link SDK, it uses path-based URLs and regular expressions based on that. For example, the path pattern {scheme}// say/:title/:message requires two regular expressions—one for the slash- delimiter and one for extracting parameter names.
  • Prefer query-based URLs for standard parsing. Parsing using character-based delimiters is faster than using regular expressions.
  • Support deep-linking callbacks in your URLs to help the user complete the intent. A good idea is to support three options: success, failure, and cancel.
    For example, if you have a photo editor app, it will be great to let the user go back to the photo app with the edited photo. As another example, if your app is used for authentication, provide an option to take the user back to the source app with details of whether the login was successful, was cancelled, or failed.
    The x-callback-url specification provides support for these callbacks.
  • Prefer deep-linking continuation in your URLs to help the user define a work‐
    flow that may require coordination across multiple apps.
    For example, the user may want to accomplish the following:

    1. Capture a photo.
    2. Edit the photo.
    3. Mail the updated photo to family and friends.
    4. Share the updated photo on social media.
    5. And finally, return to the photo app to capture the next photo.

      The first app can define the list of apps to deep link into and a final done deep link to be called after the entire process is complete.

  • Do not put any sensitive data in the URLs. Specifically, do not use any auth tokens. These tokens could be hijacked by an unknown app.
  • Do not trust any incoming data. Always validate the URLs. As an additional measure, it may be a good idea to expect the app to sign the data before passing it on and to validate the signature before processing. However, for this to happen securely, the private key must be kept on the server, and as such it requires net‐ work connectivity.
  • Use sourceApplication to identify the source. This is very useful in the event that you have a whitelist of apps from which you can always trust the data. Use of sourceApplication is not orthogonal to signature validation. This can be the first step before URL processing commences.


Pasteboards can be either public or private. Each pasteboard must have a unique name.
A pasteboard can hold one or more entities, which are known as pasteboard items. Each item can have multiple representations.

A pasteboard has the following benefits over deep linking:

  • It has the capability to support complex data like images.
  • It has support to represent data in multiple forms which can be used based on the target’s app capabilities. For example, a messaging app can use plain-text format, whereas a mail app can use rich-text format from the same pasteboard item.
  • The pasteboard content can persist even after app shutdown.


  • using a pasteboard is that the format of the data shared is not in any standard format
  • plain-text data can be shared using multiple options; at times, it may get confusing as to which format to use.
  • unlike deep linking, a pasteboard cannot be used to detect if the target application is installed or not. This information helps create a better user experience

Best Practices:

  • A pasteboard is essentially an interprocess communication mediated by the pas‐ teboard service. All security rules of IPC apply (i.e., do not send any secure data, do not trust any incoming data).
  • Because you do not control which app accesses the pasteboard, using it is always insecure unless the data is encrypted.
  • Do not use large amounts of data in a pasteboard. Although pasteboards have support for exchanging images and for multiple formats, keep in mind that each entry not only consumes memory but also takes extra time to write and read.
  • Clear the pasteboard when the app is about to enter the background by using either a UIApplicationDidEnterBackgroundNotification notification or a UIAp plicationWillResignActiveNotification notification. Better still, you could implement the corresponding callback methods of UIApplicationDelegate.
    You can clear the pasteboard by setting the items to nil, as shown here: myPasteboard.items = nil;
  • To prevent any kind of copy/paste, subclass UITextView and return NO for the copy: action in canPerformAction.

Sharing Content

Document Interaction

There are two sides to using UIDocumentInteractionController: the publisher and the consumer.

As a publisher, your app publishes the document to be viewed. It is the controller’s responsibility to load the target app, make the content available to the consumer app, and finally take the user back to the host app. The following subsection presents rep‐ resentative code for the publisher.

As a consumer, your app’s responsibility includes processing the document (and rendering the result). It is also responsible for performing some cleanup.


There are two types of activities: actions and shares

Shared Keychain

A shared keychain is another option for sharing data among your apps securely. Only the apps that belong to the same group ID and are signed using the same certificates can share the data.

The only way to implement single sign-on across all your apps is using a shared key‐ chain.

This is also the only option for sharing data across apps from the same publisher (same signing certificate) that does not require invoking another app from the one being used by the user.

Because the data is encrypted, it should be the place to store secure information such as credentials, credit card number (though without CVV), and so on. Avoid flushing in a lot of generic, nonsecure data because the access is slower than to non-encrypted data.


iOS 8 introduced four new options for sharing content across apps under the broader category of what is known as application extensions.[from page 274~289]


For a deeper study

  • Hacking and Securing iOS Applications: Stealing Data, Hijacking So ware, and How to Prevent It by Jonathan Zdziarski (O’Reilly)
  • iOS Hacker’s Handbook by Charlie Miller et al. (Wiley)
  • iOS Application Security: e De nitive Guide for Hackers and Developers by David Thiel (No Starch Press)

App access

How to make access to your app secure, how to manage identity, and other related topics.

Anonymous Access

There are two options available for identifying a device: Identifier for Vendor (IDFV) or Identifier for Advertiser (IDFA). Let’s take a closer look at each of these.

Retrieving the IDFV

-(NSString *)idfv {
	UIDevice *device = [UIDevice currentDevice]; 
NSUUID *rv = device.identifierForVendor;
while(!rv) { [Thread sleepForTimeInterval:0.005]; rv = device.identifierForVendor; }
return rv.UUIDString;

Retrieving the IDFA

-(NSString *)idfa {
	ASIdentifierManager *mgr = [ASIdentifierManager sharedManager];
if(mgr.isAdvertisingTrackingEnabled) { UUID *rv = mgr.advertisingIdentifier;
while(!rv) { [Thread sleepForTimeInterval:0.005]; rv = mgr.advertisingIdentifier; } return rv.UUIDString;
} return nil;

Authenticated Access

  • App passcode
  • Game Center
  • third-party authentication
  • Your own authentication

Network security

This includes everything that you do talking to the servers.


Use Certificate Pinning

HTTPS is not a cure-all—adopting it will not magically make all your communica‐ tions secure. The basis of HTTPS is the trust in the public key that is used to encrypt the initial message (during the SSL handshake). A man-in-the-middle (MITM) attack involves being able to capture the key used to encrypt the messages.

Certificate pinning works is that the app creates a custom trust level by trusting only one or a few certifi‐ cates that can be the root certificates for your app. This allows trusting only the certif‐ icates from a whitelist, which ensures that an unknown certificate that would allow network monitoring cannot be installed on the device.

typedef void(^HPResponseHandler)(NSURLResponse *, NSError *error);

@interface HPPinnedRequestExecutor

@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, copy) HPResponseHandler handler;


@interface HPPinnedRequestExecutor () <NSURLConnectionDelegate>

@property (nonatomic, readwrite) NSURLRequest *request;


@implementation HPPinnedRequestExecutor

-(instancetype)initWithRequest:(NSURLRequest *)request {
if(self = [super init]) {
self.request = request;
return self;

-(void)executeWithHandler:(HPResponseHandler)handler {
self.handler = handler;
[[NSURLConnection alloc] initWithRequest:self.request delegate:self];

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//Do regular stuff, send result using handler

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace*)space {
return [NSURLAuthenticationMethodServerTrust isEqualToString:space.authenticationMethod];

- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

void (^cancel)() = ^{
[challenge.sender cancelAuthenticationChallenge:challenge];
isEqualToString:challenge.protectionSpace.authenticationMethod]) {

SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
if(serverTrust == nil) {

OSStatus status = SecTrustEvaluate(serverTrust, NULL);
if(status != errSecSuccess) {

SecCertificateRef svrCert = SecTrustGetCertificateAtIndex(serverTrust, 0);
if(svrCert == nil) {

CFDataRef svrCertData = SecCertificateCopyData(svrCert);
if(svrCertData == nil) {

const UInt8* const data = CFDataGetBytePtr(svrCertData);
const CFIndex size = CFDataGetLength(serverCertificateData);
NSData* cert1 = [NSData dataWithBytes:data length:(NSUInteger)size];
if(cert1 == nil) {

NSString *file = [[NSBundle mainBundle]
NSData* cert2 = [NSData dataWithContentsOfFile:file];
if(cert2 == nil) {
if(![cert1 isEqualToData:cert2]) {
useCredential:[NSURLCredential credentialForTrust:serverTrust]



implemented for an NSURLSession

implemented for AFNetworking

Local storage

All about data on the device.

  • Local storage is not secure
  • Encrypt local storage
  • User defaults (NSUserDefaults) are not safe
  • App Bundle (NSBundle) values are not safe either
  • Don’t completely rely on the keychain
  • Be careful what you log

Data sharing

basic rule to follow when sharing data and processing incoming data is this: do not trust the other side. When receiving data, always validate.

Security and App Performance

Every extra layer of encryption or safety measures that you add counts toward the overall memory consumed and processing time. There is no way that you can opti‐ mize across all dimensions. You will have to make some trade-offs.


Description Status
Static Code Analysis
Is NSLog used? Yes/No
If so,NSLogis used only in debug builds Yes/No
All URLs are HTTPS Yes/No
Paths to local les are not hardcoded Yes/No
Dependencies are checked for the latest versions and patches Yes/No
No private APIs are used Yes/No
No private keys or secrets are embedded in the code Yes/No
No private keys or secrets are embedded in the resources Yes/No
There is no unreachable or dead code Yes/No
Entitlements are correct (none missing, none extra) Yes/No
If using theconnection:willSendRequestForAuthenticationChallenge:method, there is no direct branch (without any code) touseCredential:forAuthenticationChallenge: Yes/No
App uses IDFV Yes/No
App uses IDFA Yes/No
Correct provisioning pro le/certi cate is con gured for app signing Yes/No
There are checks against SQL injection Yes/No
Runtime Analysis—Log
Logging is done only to file Yes/No
Log files are deleted periodically Yes/No
Log rotation is implemented Yes/No
There are no secrets or sensitive information in the log Yes/No
No sensitive information is logged when stack trace is printed Yes/No
Runtime Analysis—Network
Only HTTPS URLs are used Yes/No
Server has implementation against CRIME attack Yes/No
Server and client app have implementation against BREACH attack Yes/No
Client app uses certi cate pinning Yes/No
Correct caching policy is set up Yes/No
Runtime Analysis—Authentication
App uses third-party authentication Yes/No
App uses custom authentication Yes/No
Third-party auth SDK is well audited against the remainder of this checklist Yes/No
Login UI masks password Yes/No
Password is not copyable Yes/No
Access token is stored in keychain Yes/No
App implements passcode Yes/No
Passcode is stored in keychain Yes/No
It is possible to change authentication work ow through a server config Yes/No
Runtime Analysis—Local Storage
App uses local storage Yes/No
Any sensitive information is encrypted Yes/No
Storage is cleaned up periodically Yes/No
Runtime Analysis—Data Sharing
App uses shared keystore to keep common settings Yes/No
Deep link URLs are validated Yes/No
Any incoming data is validated Yes/No
No sensitive data is shared to an unknown app Yes/No
Correct group IDs are con gured when using the app extensions Yes/No

checklist references