Network

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.

Latency

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.

Example

//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)); 
    res.send(body);
});

//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

Servers

  • 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
    experience).

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.

Request

  • 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.

Tools

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

Charles

  • 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

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.

Drawback:

  • 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.

Activities

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.

Extensions

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]


Security

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

1
2
3
4
5
-(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

1
2
3
4
5
-(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 HTTPS

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
typedef void(^HPResponseHandler)(NSURLResponse *, NSError *error);

@interface HPPinnedRequestExecutor

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

@end

@interface HPPinnedRequestExecutor () <NSURLConnectionDelegate>

@property (nonatomic, readwrite) NSURLRequest *request;

@end

@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];
};
if([NSURLAuthenticationMethodServerTrust
isEqualToString:challenge.protectionSpace.authenticationMethod]) {

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

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

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

CFDataRef svrCertData = SecCertificateCopyData(svrCert);
if(svrCertData == nil) {
cancel();
return;
}

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

NSString *file = [[NSBundle mainBundle]
pathForResource:@"pinned-key"
ofType:@"der"];
NSData* cert2 = [NSData dataWithContentsOfFile:file];
if(cert2 == nil) {
cancel();
return;
}
if(![cert1 isEqualToData:cert2]) {
cancel();
return;
}
[challenge.sender
useCredential:[NSURLCredential credentialForTrust:serverTrust]
forAuthenticationChallenge:challenge];

}
}

@end

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.

Checklist

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

Concurrent Programming

topics:

  • Creating and managing threads
  • The Great Central Dispatch (GCD) abstraction
  • Operations and queues

Threads

Stack Size

The main thread stack size is 1 MB and cannot be changed. Any secondary thread is allocated 512 KB of stack space by default.

Note that the full stack is not immediately created. The actual stack size grows with use

Before a thread starts, the stack size can be changed. The minimum allowed stack size is 16 KB, and the size must be a multiple of 4 KB.

1
2
3
//Example for changing thread stack size
+(NSThread *)createThreadWithTarget:(id)target selector:(SEL)selector object:(id)argument stackSize:(NSUInteger)size {
	if( (size % 4096) != 0) { 
return nil; } NSThread *t = [[NSThread alloc] initWithTarget:target selector:selector object:argument]; t.stackSize = size; return t;
}

GCD

review of GCD fundamentals, check out Ray Wenderlich’s Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial

In the scenarios where your app has multiple long-running tasks to be executed concurrently, it is better to take control of the thread creation. If your code takes longer to complete, you may soon hit the limit of 64, the maximum GCD thread pool size.

Operations and Queues

comparison of the NSThread, NSOperationQueue, and GCD APIs:

GCD

  • Highest abstraction.
  • Two queues are available out of the box: main and global.
  • Can create more queues (using dispatch_queue_create).
  • Can request exclusive access (using dispatch_barrier_sync and dispatch_bar rier_async).
  • Manages underlying threads.
  • Hard limit on 64 threads created.

NSOperationQueue

  • No default queues.
  • App manages the queues it creates.
  • Queues are priority queues.
  • Operations can have different priorities (use the queuePriority property).
  • Operations can be cancelled using the cancel message. Note that cancel is merely a flag. If an operation is under execution, it may continue to execute.
  • Can wait for an operation to complete (use the waitUntilFinished message).

NSThread

  • Lowest-level construct, gives maximum control.
  • App creates and manages threads.
  • App creates and manages thread pools.
  • App starts the threads.
  • Threads can have priority. OS uses this for scheduling their execution.
  • No direct API to wait for a thread to complete. Use a mutex (e.g., NSLock) and custom code.

Locks

Locks are the basic building blocks to enter a critical section. atomic properties and @synchronized blocks are higher-level abstractions available for easy use.

NSLock

NSLock must be unlocked from the same thread where it was locked. NSLock does not allow lock to be called more than once without first calling unlock.

NSRecursiveLock

NSRecursiveLock, as the name indicates, does allow lock to be called more than once before it is unlocked. Each lock call must be matched with an equal number of unlock calls before the lock can be considered released for another thread to acquire.

NSCondition
A thread can wait on a condition that releases the lock. Another thread can sig nal the condition by releasing the same lock and awakening the waiting thread. The standard producer–consumer problem can be solved using NSCondition.

Use Reader–Writer Locks for Concurrent Reads and Writes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface HPCache ()
@property (nonatomic, readonly) NSMutableDictionary *cacheObjects; @property (nonatomic, readonly) dispatch_queue_t queue;

-(id)objectForKey:(id) key;
-(void)setObject:(id)object forKey:(id)key;

@end
@implementation HPCache

-(instancetype)init {
if(self = [super init]) { _cacheObjects = [NSMutableDictionary dictionary]; _queue = dispatch_queue_create(kCacheQueueName, DISPATCH_QUEUE_CONCURRENT); } return self;
}

-(id)objectForKey:(id<NSCopying>)key {
__block id rv = nil;
//Use dispatch_sync (or dispatch_async) for operations that do not modify state. dispatch_sync(self.queue, ^{ rv = [self.cacheObjects objectForKey:key]; });
return rv;
}
-(void)setObject:(id)object forKey:(id<NSCopying>)key {
//Use dispatch_barrier_sync (or dispatch_barrier_async) for operations that may modify state.
dispatch_barrier_async(self.queue, ^{ [self.cacheObjects setObject:object forKey:key]; });
}

Use Immutable Entities

Best practices:

  • Use immutable entities.
  • Support them with an updater subsystem.
  • Allow observers to receive notifications on data changes.

Have a Central State Updater Service

The model layer was built based on three principles:

  • Immutability
  • Denormalized storage
  • Asynchronous, opt-in consistency

Application Lifecycle

App Delegate

The App Life Cycle

Application Launch

There are four types of application launches possible:

  • First launch
  • Cold start
  • Warm (re)start
  • Launch a er upgrade

First launch

Although each subsystem may be individually performant, their performance may drop when used together. For example, if multi‐ ple components attempt to simultaneously read from the filesys‐ tem, it will result in an overall sluggishness.

How can we optimize the performance if these (possibly mandatory) initializations add up to a long initialization time?

follow these concrete steps to split down the task list for more effective performance:

  • Identify what must be executed before the UI can be shown.

    If the app is being launched for the first time, there is no need to load any user preferences such as theme, refresh interval, cache size, and so on. There will be Application Launch | 153
    no custom values. It is OK to let the initial cache grow wild, as we know that it will not grow beyond a fraction of the final intended limit.
    The crash reporting system should be the first one to be initialized.

  • Order the tasks.

    Ordering is very important—not only because the tasks may have interdependen‐ cies but also because it may save you precious user time.

  • Split the tasks into two categories—tasks that must execute in the main thread only and tasks that can execute in other threads—and execute them accordingly.

    It is possible to further split the tasks that can be executed in a non-main thread into those that can be executed concurrently and those that cannot be.

  • Other tasks can either be executed after the UI is loaded or may be fired asyn‐ chronously.

    Delay initialization of other subsystems, such as loggers and analytics. It may be possible to queue operations (e.g., writing log messages or tracking events) until these subsystems are completely initialized, which may happen late in the app’s life.

Example Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@interface HPInstrumentation ()

@property (nonatomic, copy) BOOL initialized;
@property (nonatomic, strong) NSMutableArray *events;
@property (nonatomic, strong) dispatch_queue_t queue;

-(void)markInitialized;
+(void)logEventImpl:(NSString *)name;

@end

static HPInstrumentation *_instance;

@implementation HPInstrumentation

+(HPInstrumentation *)sharedInstance {
return _instance;
}

+(void)setSharedInstance:(HPInstrumentation *)instance {
_instance = instance;
}

+(void)logEvent:(NSString *)name {
[[HPInstrumentation sharedInstance] logEventImpl:name];
}

-(instancetype)initWithAPIKey:(NSString *)apiKey {
if(self = [super init]) {
self.initialized = NO;
self.events = [NSMutableArray array];
self.queue = dispatch_queue_create("com.m10v.queue.analytics", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[Flurry startSession:apiKey];
self.delegate = [[HPInstrumentationUseList alloc] init];
//Code below is for after initialization
dispatch_sync_barrier(self.queue, ^{
for(NSDictionary *name in self.delegate.events) {
[Flurry logEvent:name];
}
self.delegate = [[HPInstrumentationUseSDK alloc] init];
});
});
}
return self;
}

-(void)logEventImpl:(NSString *)name {
dispatch_sync(self.queue, ^{
[self.delegate logEvent:name];
});
}

@end

//HPAppDelegate.m
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *) launchOptions {
HPInstrumentation *analytics = [[HPInstrumentation alloc] initWithAPIKey:@"API_KEY"];
[HPInstrumentation setSharedInstance:analytics];
[HPInstrumentation logEvent:@"App Launched"];
}

Cold Start

it’s important to know the following:

  • Minimum number (min) of entries required to show a usable and meaningful UI
  • Time it takes to load M entries from local cache (let’s call it tl)
  • Time it takes to get latest M entries from remote server (let’s call it tr)
  • Maximum number (max) of entries you will ever keep in memory at any given point in time for speedier access, especially during fast swipes and scrolls

f we cannot load M entries in 3 seconds, the user experience degrades significantly. why 3seconds

Warm Launch

There are two scenarios for warm launch:

  • User taps the icon
  • App receives a deep link

Launch After Upgrade

The following best practices can be applied to inform the user about what is about to happen to the local data.

  • When the local cache can be used, inform the user about it. Do not inform the user if there is no migration required because then the use of the local cache is implicit.
  • If the data must be migrated and the process may take several minutes, give an option to postpone it.
  • When the local cache must be discarded because it is easier and faster to retrieve data from the server, inform the user about it.
    A case in hand is when migrating several records, say in a mail app, to the upda‐ ted schema in the newer version of the app may be more complex than retrieving them from the server.

Push Notifications

Remote Notifications

  • If the app is active, it receives the notification via the didReceiveRemoteNotifica
    tion callback.
    No other callbacks are invoked. No UI is presented to the user to avoid any dis‐
    traction.
  • If the app is backgrounded or stopped, only the silent push notification callbacks are triggered.
    Based on the notification settings, non-silent push notifications may appear in the notification center or as alerts and/or update the app icon’s badge counter.
  • When the user opens a notification by interacting with it using the notification center or an alert, one of the following may happen:

    1. If the app was backgrounded, the notification callback method is invoked.
    2. If the app was stopped, the notification object (NSDictionary) is available in the launchOptions parameter to the method application:didFinishLaun chingWithOptions:.

application:didReceiveRemoteNotification:fetch CompletionHandler:, if implemented, can be called even if the app is in the background and can even start the app if it is not already running. Such notifications are known as silent push notifications.

It is also important to note that the latter method may be called twice:

  • First when the notification is received and the payload contains the key content-available with value 1
  • Second when the user interacts with the notification, either in the notification center or an alert

use application state to resolve this problem

1
2
3
4
5
6
7
8
9
10
11
12
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
//process remote notification - app running
if(application.applicationState == UIApplicationStateInactive) {
//user tapped the notification in notification center or the alert
[self processRemoteNotification:userInfo];
} else if(application.applicationState == UIApplicationStateBackground) {
//App in background, no user interaction - just fetch data
}else{
//app is already active - show in-app update
}
}

Local Notifications

Unlike remote notifications, local notifications will not show any UI when the app is in use.

Use local notifications in conjunction with silent push notifications to make the app responsive and usable faster on the next launch.

Background Fetch

There are three basic steps to enable background fetch:

  1. Enable the capability in your project settings.
  2. Set the refresh interval, preferably in application:didFinishLaunchingWithOptions. Use the -UIApplication setMinimumBackgroundFetchInterval: method to request that the refresh be done at the specified frequency.
  3. Implement the application:performFetchWithCompletionHandler: app delegate method. If the task is not complete within 30 seconds, the OS will schedule the method execution less frequently.
    On a practical note, the typical time that the app may get is much smaller, generally in the range of 2–4 seconds. The Apple developer website lists 30 seconds as the upper-bound limit.

Summary

At times, actual performance may be less important than the perceived performance. Using silent notifications and background fetches to warm up the app to keep it ready for the next use is a smart way to achieve a positive perception of your app.

Now that we’ve covered optimization techniques for various scenarios, including first launch, cold start, warm start, and launch after upgrade, you should be able to mini‐ mize the startup time and make your app usable as quickly as possible.


User Interface

There are external factors that you cannot control

  • Network

    Poor network conditions increases the time taken to synchronize.

  • Hardware

    Better hardware makes for better performance

  • Storage

    The app may run on devices with varying storage ranging from 16 GB to 128 GB, which limits the offline cache that your app can store.

View Controller

basic best practices to follow when structuring view controllers:

  • Keep the view controller lightweight. In an MVC app, the controller is only glue. This is not where the entire business logic lies. It does not even belong to the model. The business logic belongs to the service or business logic component. Keep it there.
    The view controller should bind the service component to the views by means of what can be termed action delegates or service providers, which should preferably be injected into the controller
    • Do not write animation logic in the view controller. This may be in an independ‐ ent animation class that accepts views to apply animations on. View controllers attach the animations to the views or transitions.
      Special-purpose views may own their own animations. For example, a custom spinner controller will have its own animation.
    • Use data source and delegate protocols to separate the code pertaining to data retrieval, update, and other business logic. View controllers are restricted to pick‐ ing correct views and connecting them to the feeders.
    • View controllers respond to events from the views—for example, button click or table cell selection—and connect them back into data sinks.
    • View controllers respond to UI-related events from the OS—for example, orien‐ tation changes or low-memory warnings. This may trigger relayout of the views.
    • Do not write custom init code. Why? Well, what if your view controller is repur‐ posed to an XIB or storyboard? The init method will never be called.
    • Do not handcraft the UI in the view controller using code. Do not implement all UI, view creation, and view layout logic in the view controller. Use nibs or story‐ boards.
      Handcrafted code does not last long, especially as the app grows and designs change. It is faster to redesign using Interface Builder than to hand code pixel coordinates.
      In addition, the app may run on devices with different sizes and form factors. It is difficult to scale custom code to work with all form factors, handle rotation dur‐ ing orientation changes, and keep pace with new design paradigms that evolve every couple of years or so
      Also, when you have the design separated out in independent nibs and story‐ boards, you will have the flexibility to run A/B testing where it is easy to choose between different layouts.
    • Prefer creating a base view controller with common setup and have other view controllers inherit from this.
      This technique is not always possible, because there may be a need to inherit from different view controllers at different parts of the app. For example, you should use UITableViewController for the contacts list and UIViewController for the user profile.
      However, if you have multiple places where you need to show content in a UIWeb View, a base view controller will work well. If you need to display the privacy pol‐ icy URL or terms and conditions page, you do not need to subclass. However, if you need to show an image or video that a user shares (in a messaging app), you can create subclasses that can define custom chrome or control overlays.
    • Use categories for creating reusable code across view controllers. In case a parent view controller does not suffice (for example, because you need different types of view controllers in your app), create categories and add your custom methods or properties there.
      That way, you are not restricted to using a predefined base class and still get the benefit of reusability.

View Load

loadView

  • Do set the view property to the root of the view hierarchy.
  • Ensure that the views are now shared with any other view controllers.
  • Do not call [super loadView].

viewDidLoad

  • Configuring data sources to populate data.
  • Binding data to views.

    This is a debatable item. Depending on the use case, you may bind data once and have a refresh button, or bind each time viewWillAppear is called. The upside of doing the latter is that the UI always has the latest data. The downside is that if the data does not update frequently (e.g., in a news app), the user may see an unnecessary refresh each time (say, when the UITableView rebinds).

  • Binding view event handlers, data source delegates, and other callbacks.
  • Registering observers on data.

    Depending on where you bind data to the views, observers on data may also change.

  • Monitoring for notifications from the notification center.
  • Initializing animations.

View Hierarchy

View construction and rendering involves the following steps:

  1. Construct subviews.
  2. Compute and apply constraints.
  3. Perform steps 1 and 2 recursively for the subviews.
  4. Render recursively.

Our focus now will be not only to get as much as possible of the main thread execu‐ tion done within 16 ms, but also to minimize the number of frames dropped (or bulk drop of frames, to be more explicit).

View Visibility

Some best practices for effectively using these lifecycle events are given next

  • Do not override loadView. Enough said.
  • Use viewDidLoad as the last checkpoint to see if the data from data sources is available. If so, update the UI elements.
  • Use viewWillAppear: to update the UI elements—but only if you really want to always show the latest details.
    For example, in a messaging app, if the user returns to the message list in the chat session after watching a shared video, you would want to refresh it with the latest messages.
    However, in a news app, you may not want to immediately refresh the list with all the new articles, lest the user lose the context. In the latter case, the table view view controller will generally listen to the events from the data source and prefer to make subtle and infrequent updates to the list of new articles.
  • Use viewDidAppear: to start animations. If you have streaming content such as video, play that. Subscribe to application events to detect if the animation/video or any other processing that continuously updates the video should continue or not.
    It is not advisable to update the UI here with the latest data. If you do so, the final effect will be that the user transitions into an old UI followed by an update after the transition animation is complete, which may not be a great experience.
    Having said that, there may still be use cases that force you to perform UI updates in viewDidAppear:. If the user experience is acceptable, go ahead with it.
  • Use viewWillDisappear: to pause or stop animations. Again, do nothing more.
  • Use viewDidDisappear: to tear down any complex data structures that you may
    have held in memory.
    This is also a good time to unregister for any notifications from the data sources that the view controller may be bound to and also with the notification center for the app events that will have been connected to animations, data sources, or other UI updates.

View

basic rules to follow

  • Minimize work done in the main thread. Any extra code to be executed means higher chances of dropping a frame. Too many frames dropped will introduce jitter.
  • Avoid fat nibs or storyboards. Storyboards are great, but the entire XML must be loaded (I/O) and parsed (XML processing) before it can really be used. Minimize the number of units that go into storyboards.
    If needed, create multiple storyboards or nib files. This will ensure that all the screens are not loaded in one go during app launch, but are loaded as needed. This not only helps the app start-up time but also keeps the overall memory requirement lower. Example in Twitter
  • Avoid multiple layers of nesting in the view hierarchy. Try to keep it as flat as possible. Nesting is a necessary evil, but still an evil.
    Each time a view is added anywhere in the hierarchy, its ancestor tree receives a setNeedsLayout: with a value of YES that causes layoutSubviews: to be invoked when the event queue is processed. This is an expensive call because the view has to recompute the positions of the subviews using the constraints, and it happens for each level in the ancestor tree.
  • Lazy-load the views and reuse them wherever possible. The more views you have, the longer it takes to not only load but also render them, which impacts both memory and CPU usage.
    If needed, create your own view cache. This may be over and above the cell-reuse support already provided in UITableView and UICollectionView. These con‐ tainers will let the view be dealloced when not in the viewport. If the view con‐ struction is complex and takes time, implementing a custom view cache is advisable.
    What if you use UIScrollView? Definitely lazy-load. Load only the views required for scroll position 0, and then mimic UITableView behavior by building your own view cache. Use delegate’s scrollViewDidScroll: in conjunction with the contentOffset (scroll position) property to know which views are to be rendered.
    As a general practice, render elements up to the screen height beyond the view‐ port to avoid any jitter during scroll since they need to be rendered in quick succession as the scrolling starts.
    And keep this at the back of your mind: UITableView inherits from UIScroll View, which means if UITableView can do smart view caching, so can your cus‐ tom code.
  • Prefer custom drawing for complex UIs. It results in one view to be drawn instead of multiple subviews, and avoids costly layoutSubviews and drawRect: calls.
    In addition, you avoid the cost of using general-purpose, feature-rich compo‐ nents by using optimized views for optimized direct drawing.

UILabel

steps to render UILable

  1. Using the font family, font style, and text to be rendered, compute the number of pixels it requires. This is an extremely costly process and it should be done as sparingly as possible.4
  2. Check against the width of the available frame to render.
  3. Check against the numberOfLines to compute the number of lines to show.
  4. Was sizeToFit called? If so, compute the height.
  5. If sizeToFit was not called, check if content can be shown for the given height of the frame.
  6. If the frame is not sufficient, use lineBreakMode to determine the wrap or truncation location.
  7. Take care of other configuration options (e.g., if it is plain text or attributed, shadows, alignment, autoshrink, etc.).
  8. Finally, use the font, style, and color to render the nal text to be shown.

If the width is a fraction, everything will work great except that the rendering requires anti-aliasing, which is an expensive operation.

UIButton

There are four ways to render a button:

  • Default rendering with custom text
  • Button with full-sized assets
  • Resizable assets
  • Using CALayer and Bézier paths for custom drawing

presents the pros and cons of working with each option for rendering a button.

“Designing for iOS: Taming UIButton”.

UIImageView

best practices to maximize performance when working with UIImage and UIImageView:

  • For known images, use the imageNamed: method to load the images. It ensures that the content is loaded in memory only once and repurposed across multiple UIImage objects.
  • Use an asset catalog for loading bundled images with the imageNamed: method. This is especially useful if the app has a bunch of icons, each one of which may be small in size. Feel free to create multiple catalogs of related images (i.e., images that are generally used together).
    When iOS loads from the disk, there is an optimal buffer size that can be used to load multiple images in a single read. Also, opening multiple I/O streams has overheads as compared to opening one stream and reading multiple images from there. It is generally faster to read one combo file that is 32 KB in size than 16 files that are each 2 KB in size.
    However, if you want to load a large image that will be used only once, or at best very sparingly, consider using imageWithContentsOfFile: instead of using an asset catalog and the imageNamed: method, because the asset catalog caches the images (which is not needed in this case).
    In an app that I worked on, the team saw a reduction of about 300 ms in initial load time by choosing to bundle the images for the initial two screens in one asset catalog.
  • For other images, use a high-performing image cache library. AFNetworking and SDWebImage are great libraries to use.
    When working with an in-memory image cache, be sure to configure memory usage parameters correctly. Do not hardcode. Make it adaptive—a percentage of available RAM is a good way to configure it.
  • Load the image of the same size as the UIImageView to be rendered. You get the maximum performance when the dimensions of the image parsed and of the UIImageView are the same—resizing images is an expensive operation, and it is even costlier if the image view is contained in a UIScrollView.
    If the image is downloaded from the network, try to download the image that matches the view size. If that option is not available, preprocess the image to resize it appropriately.
  • If there is a need to apply effects like blur or shades, create a copy of the image contents, apply the effects, and use the final bitmap to create the final UIImage. That way, the effects are only applied once and the original image can be used for other displays if needed.
  • Whatever technique you use to load the images, execute it off the main thread, preferably in a dedicated queue.
    Specifically, decompress JPG/PNG images off the main thread.
  • Last but not the least, determine whether you really need the images. If you were to show a rating bar, you might be better off with a custom view with direct drawing than using multiple images with transparency and overlays.

UITableView

best practices to keep in mind when working with UITableView:

  • In the dataSource method tableView:cellForRowAtIndexPath:, use table View:dequeueReusableCellWithIdentifier: or tableView:dequeueReusable CellWithIdentifier:forIndexPath: to reuse cells instead of creating new cells each time.
    Cell creation has a performance cost. The cost grows multifold if several cells have to be created in a short span of time—for example, when the user scrolls the table view. Also, as cells go out of scope, they are dealloced, resulting in a double whammy. Reusing cells means the only overhead will be to render the cells.

  • Avoid dynamic-height cells as much as possible. Fixed, predetermined heights means less computation. With dynamically configured content, not only must the height be computed when required, but the cell contents have to flow and lay‐ out must be performed each time the view is rendered. That can be a big perfor‐ mance hit.

  • If you really need dynamic-height cells, define a rule to mark the cells dirty. If a cell is dirty, compute the height and cache it. Continue to return the cached height in the tableView:heightForRowAtIndexPath: callback of the delegate until the cell is not dirty.
    If the model to render is immutable, a simple rule that can be used is to check if the model currently being rendered is the same as the one at the corresponding indexPath. If so, the same values will be rendered and hence it requires no fur‐ ther processing. If not, recompute the values and attach the new object (model) to the cell.
  • When reusing the cells using custom views, avoid laying them out each time by calling layoutIfNeeded it is requested.
    Even if a cell is fixed-height, it is possible that the individual elements within the cell may still be configurable to be of varying height—for example, UILabel sup‐ ports multiline content and UIImageView can work with varying-sized images.
    Avoid that. Fix the size of each element. This ensures minimal time needed to render the cells.
  • Avoid nonopaque cell subviews. Whenever you create a UITableViewCell, try to only have opaque elements in it. Translucent or transparent elements (views with alpha less than 1.0) may look great but have a performance hit.
    For aesthetic reasons, you may still want to have alpha set to a value less than 1.0. If that is case, be aware of the costs.
  • Consider using shell interfaces when fast-scrolling (see Figure 6-7). When the user fast-scrolls the table view, it is possible that even with all your optimizations, the view reuse and rendering will still take well over 16 ms and there will be occa‐ sional frame drops that may result in a jittery experience.
    A good option in these situations is to use a shell interface, which can be defined as a predefined interface whose only purpose is to indicate to the end user that there is some data to be shown. As the scrolling velocity drops down below a threshold, flush in the final view and populate it with the data.
    You can get the velocity using the panGestureRecognizer property associated with the table view
1
2
3
4
-(void)scrollViewDidScroll:(UIScrollView *)scrollView { 
CGPoint velocity = [tableView.panGestureRecognizer velocityInView:self.view]; self.velocity = velocity; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(fabs(self.velocity.y) > 2000) { //return shell cell }else{ //return real cell }
}
  • Avoid gradients, image scaling, and any offscreen drawing. These effects are a drain on the CPU as well as the graphics processing unit (GPU).

UIWebView

best practices to keep in mind while working with UIWebView

  • UIWebView can be bulky and sluggish. Reuse web view objects as much as possi‐ ble. UIWebView is also known to leak memory. So, one instance per app should be good enough.
    Whenever you want to present a new URL to the user, reset the content to empty HTML. This ensures that the web view does not show previous content to the end user. Use loadHTMLString:baseURL: followed by loadRequest: to accomplish this.
  • Attach a custom UIWebViewDelegate. Implement the webView:shouldStartLoad WithRequest:navigationType: method. Watch out for the URL scheme. If it is anything other than http or https, beware: your app knows how to handle it or warn the user that the site is trying to let her out of the app.
    This is a great option not only to ensure that the user does not abruptly land in another app, but also for safeguarding against malicious content, especially if you happen to show content from an unknown URL—say, in a mail or messaging app.
  • You can create an app-to-JavaScript bridge by using the stringByEvaluatingJavaScriptFromString: method to execute JavaScript in the currently loaded web page. To call a method into a native app, use custom URL schemes and refer to the preceding bullet point on how to handle them.
  • Implement the method webView:didFailLoadWithError:of the delegate to keep a close track of any and all errors that may occur.
  • Implement the webView:didFailLoadWithError: method to handle specific errors. The NSError object has meaningful insights if the domain equals NSURLErrorDomain.
1
2
3
4
5
6
7
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
if([NSURLErrorDomain isEqualToString:error.domain]) {
switch(error.code) { case NSURLErrorBadURL:
//handle bad URL break; case NSURLErrorTimedOut: //handle timeout break;
//... etc. }
}
}
  • UIWebView will not notify of any HTTP protocol errors like a 404 or 500 response. You will have to make a double call, first using a custom NSURLConnection call and then by the web view. Provide a dele‐ gate to the NSURLConnection and implement connection:didReceiveResponse: to get response details.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@interface HPWebViewController() <UIWebViewDelegate, NSURLConnectionDataDelegate>
@property (nonatomic, assign) BOOL shouldValidate;
@end

@implementation HPWebViewController
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType) navigationType {
if(self.shouldValidate) {
[NSURLConnection connectionWithRequest:request delegate:self];
return NO;
}
return YES;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSInteger status = [(NSHTTPURLResponse *)response statusCode];
if(status >= 400) {
//Bingo! An error.
//Show alert or hide web view - don't show bad page.
}else{
self.shouldValidate = NO;
[self.webView loadRequest:connection.originalRequest];
}
[connection cancel];
}
@end
  • The container where you embed the UIWebView should provide the following:

    1. Navigation buttons (back and forward)
    2. Reload button
    3. Cancel button to cancel the page currently being loaded
    4. UILabel to show the title of the page
    5. Close button to move out from the web view, unless that is the only UI that you have in your app—for example, in a hybrid app

Custom Views

When Should I Set layer.shouldRasterize to YES?

As you may notice, there is a staggering performance difference of a factor of 2x–20x. The initial load is blazingly 50x faster when using direct draw.

And that is when the code for direct draw is not even optimized.
Thus, from a performance perspective, direct drawing should offer better performance at times—orders of magnitude better than composite views.

However, from a maintenance perspective, the code can be difficult to maintain and evolve. Once you have stabilized the app, there will be a definitive case to move away from a composite UI to direct drawing.

Auto Layout

In general, directly setting the frames is around 1,000x faster than using Auto Layout.
Auto Layout Performance on iOS // source code for the test

Size Classes

Size classes require Auto Layout. If you choose not to use Auto Layout for performance reasons, you will not be able to use size classes.

Defining Performance

be related one or more of the considerations

  • performance metrics (what we want to measure and monitor)

  • measurement (actually collecting the data)

Performance Metrics

  • memory(Chapter 2)

  • power consumption(Chapter 3)

  1. an extremely important factor
  2. not only minimize power consumption but also ensure that the user experience is not degraded
  3. Your data structures and algorithms must be efficient in terms of execution time and CPU
    resources, but you also need to take into account various other factors.
  • initialization time(Chapter 5)
  1. Check if the app is being launched for the first time.
  2. Check if the user is logged in.
  3. If the user is logged in, load previous state, if applicable.
  4. Connect to the server for the latest changes.
  5. Check if the app was launched with a deep link. If so, load the UI and state for the deep link.
  6. Check if there are pending tasks from the last time the app was launched. Resume them if need be.
  7. Initialize object and thread pools that you want to use later.
  8. Initialize dependencies (e.g., object-relational mapping, crash reporting system, and cache).
  • execution speed(Chapter 4,6,7,11)
  • responsiveness(Chapter 4,5,6,10)
  • local storage
  • interoperability(Chapter 8)
  • network condition(Chapter 7)

must work in all of the following scenarios:

  1. High bandwidth and persistent network
  2. Low bandwidth but persistent network
  3. High bandwidth but sporadic network
  4. Low bandwidth and sporadic network
  5. No network
  • bandwidth(Chapter 7)
  • data refresh(Chapter 5,7)
  • multiuser support(Chapter 9) ignore
  • single sign-on(Chapter 9) ignore
  • security(Chapter 9) ignore
  • crashes(Chapter 12)

App Profiling

There are two ways to profile your app to measure the parameters that we have dis‐ cussed: sampling and instrumentation.

  • Sampling (Use sampling for initial performance explorations and to track CPU and memory utilization.)
  • Instrumentation (Because instrumentation involves injecting extra code, it does impact app performance—it can take a toll on memory or speed (or both).)

Measurement

  • Crash Reporting
  • Instrumenting Your App(recording user action in app)
  • Logging (CocoaLumberjack)

Memory Management

Memory consumption (i.e., how an app consumes memory)

The memory management model (i.e., how the iOS runtime manages memory)

Language constructs—we’ll take a look at Objective-C constructs and the available features you can use

Best practices for minimizing memory usage without degrading the user experience


Reference types

Strong references

A strong reference is the default reference created. Memory referred to by a strong reference cannot be relinquished. A strong reference increases the refer‐ ence count by 1, resulting in extension of the object’s lifetime.

Weak references

A weak reference is a special reference that does not increase the reference count (and hence does not extend the object’s lifetime). Weak references are a very important part of ARC-enabled Objective-C programming, as we explore later.

Other Types of References

Soft references

A soft reference is exactly like a weak reference, except that it is less eager to throw away the object to which it refers. An object that is only weakly reachable will be discarded at the next garbage collection cycle, but an object that is softly reachable will generally stick around for a while.

Phantom references

These are the weakest references in terms of strength, and are the first ones to be cleaned up. A phantomly referenced object is similar to a dealloced object, but without the memory actually being reclaimed.

These reference types do not have signifance in a reference count–based system. They are more suited for a garbage collector.


ARC also introduced four lifetime qualifiers for variables:

strong
This is the default qualifier and does not need explicit mention. An object is kept in memory as long as there is a strong pointer to it. Consider it ARC’s version of the retain call.
weak
This indicates that the reference does not keep the referenced object alive. A weak reference is set to nil when there are no strong references to the object. Consider it ARC’s version of an assignment operator, except with the added safety that the pointer is automatically set to nil when the object is dealloced.
unsafe_unretained
This is similar to
weak except that the reference is not set to nil when there are no strong references to the object. Consider it ARC’s version of an assignment operator.
__autoreleasing
Used for message arguments passed by reference using id *. It is expected that the method autorelease will have been called in the method where the argu‐ ment is passed.

Property Qualifiers

strong

Default, indicates a __strong relationship.

weak

Indicates a __weak relationship.

assign

This is not a new qualifier, but the meaning has now changed. Before ARC, assign was the default ownership qualifier. With ARC enabled, assign now implies __unsafe_unretained.

copy

Implies a __strong relationship. Additionally, it implies the usual behavior of copy semantics on the setter.

retain

Implies a __strong relationship.

unsafe_unretained

Implies an __unsafe_unretained relationship.

Because assign and unsafe_unre tained only copy over the value without any sanity check, they should only be used for value types (BOOL, NSInteger, NSUInteger, etc.). They must be avoided for refer‐ ence types, specifically pointers such as NSString and UIView .

However, when we call the method createUnsafeUnretainedPhoto again, we see not only that the object has been dealloced but also that the memory has been reclaimed and repurposed. As such, using the reference resulted in an illegal access, causing the app to crash with a signal of SIGABRT. This is possible if the memory is reclaimed at a later time (after the object deallocation but before the object access).
You will notice that the memory was reclaimed just before the title property was set, resulting in an unrecognized selector sent to instance error because the memory is gone and may be now used by some other object.

Zombies

Zombie objects are a debugging feature to help catch memory errors.
Normally when an object’s reference count drops to 0 it is freed immediately, but that makes debugging difficult. If zombie objects are enabled, instead of the object’s mem‐ ory being instantly freed, it’s just marked as a zombie. Any further attempts to use it will be logged, and you can track down where in the code the object was used past its lifetime.

Common Scenarios for Retain Cycles

  • Delegates
  • Blocks
  • Threads and timers

    clean up timer

1
2
3
 -(void)didMoveToParentViewController:(UIViewController *) parent { 
if(parent == nil) { [self cleanup]; } } -(void)cleanup {
[self.timer invalidate]; }
  • Observers

Similar to the key-value observer, the notification center does not keep a strong reference to the observer. This means that we are not responsi‐ ble for ensuring that the observer is not dealloced earlier or later than intended.

  • Returning Errors

NSError * __autoreleasing *error;

  • Finding Mystery Retains

disable ARC and add following code

1
#if !__has_feature(objc_arc)
-(id) retain {
DDLogInfo(@"%s %@", __PRETTY_FUNCTION__, [NSThread callStackSymbols]); return [super retain];
}
#endif

Memory Usage in Production

1
2
3
4
5
6
7
8
9
#import <mach/mach.h>

vm_size_t getUsedMemory() { task_basic_info_data_t info; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t) &info, &size); if (kerr == KERN_SUCCESS) {
return info.resident_size; } else {
return 0; }
}
vm_size_t getFreeMemory() { mach_port_t host = mach_host_self(); mach_msg_type_number_t size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t pagesize; vm_statistics_data_t vmstat; host_page_size(host, &pagesize); host_statistics(host, HOST_VM_INFO, (host_info_t) &vmstat, &size); return vmstat.free_count * pagesize;
}

Energy

CPU

The quantum of compu‐ tation depends on various factors:

  • Processing to be done on the data (e.g., applying formatting to the text)
  • Size of data to be processed—larger displays allow your software to present more information in a single view, which in turn means more data to be handled
  • Algorithms and data structures used to process the data
  • Number of times an update is to be performed, particularly if the data update results in the app state or UI updating (push notifications received by the app may also result in a data update, and if the user has the app open, you may need to update the UI as well)

rules to reduce the execution on the device

  • Use the optimal algorithm for the scenario
  • If the app receives data from the server, minimize the need for processing on the client side
  • Optimize ahead-of-time (AOT) processing
  • Profile energy consumption

Network

Intelligent management of network access makes your app more responsive and helps conserve battery life.

  • If no network connection is available, you should defer further attempts to access the network until a connection has been restored
  • avoid bandwidth-heavy operations, such as video streaming, unless a WiFi connection is available

cellular radio systems (LTE, 4G, 3G, etc.) are known to consume more battery than WiFi radio. This is because LTE devices use multi-input, multi-output (MIMO) technology that uses multiple concurrent signals and can maintain two LTE links. Similarly, all cellular data connections periodically scan for stronger signals.

need to:

  • Check if an appropriate network connection is available before any network operation. can use Reachability Pod
  • Continually monitor network availability and respond appropriately when the connection status changes.

Location Manager and GPS

Optimal Initialization

  • distanceFilter

    The distance filter will cause the manager to notify the delegate about location Manager:didUpdateLocations: events only if the device has moved by the mini‐ mum distance. The distance is in SI units (meters).
    This does not help reduce GPS receiver usage but does impact the processing that your app will do, and hence indirectly reduces CPU usage.

  • desiredAccuracy

    The accuracy parameter has a direct impact on the number of antennas to be used, and hence the battery consumption. Choose the accuracy level based on the specific needs of the app.

Invoke startUpdatingLocation when you need to track and stopUpdatingLocation when you do not.

startMonitoringSignificantLocationChanges when the app is backgrounded and to startUpdatingLocation when foregrounded.

1
- (void)applicationDidEnterBackground:(UIApplication *)application {
    [self.locationManager stopUpdatingLocation];
    [self.locationManager startMonitoringSignificantLocationChanges];
}
- (void)willEnterForeground:(UIApplication *)application {
    [self.locationManager stopMonitoringSignificantLocationChanges];
    [self.locationManager startUpdatingLocation];
}

In iOS 8, you need explicit permission to continue to use the location manager when the app is backgrounded. The following steps must be completed in order to get per‐ mission from the user:

  1. Update the app’s Info.plist file, adding an NSLocationAlwaysUsageDescription entry of type String. The value is the message presented to the user when requesting permissions to use location tracking when the app is in the back‐ ground or in the foreground. The key NSLocationWhenInUseUsageDescription is used for permissions to use location tracking when the app is in the fore‐ ground only.
  2. Call the method requestAlwaysAuthorization for foreground and background permissions (the method requestWhenInUseAuthorization is for foreground use only).

NSTimers, NSThreads, and Location Services

Any timers or threads are suspended when an app is backgrounded. But if you have requested location updates when your app is backgrounded, the app will be woken for an infinitesimal duration each time the updates are sent to it. And for that dura‐ tion, the threads and timers will also come to life again.

Restart After App Kill

1
-(void)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(launchOptions[UIApplicationLaunchOptionsLocationKey]) { (1)
        [self.manager startMonitoringSignificantLocationChanges]; (2)
    }
}

(1) Detect if the app was restarted due to location changes after it was killed for lack of resources.

(2) If so, start monitoring for location changes. Otherwise, start monitoring at a later appropriate time.

Animation (Screen)

you can listen to UIApplicationWillResignActiveNotification or UIApplicationDidEnterBackgroundNotification notification events to pause or stop your animations and UIApplicationDidBecomeActiveNotification notification events to resume animations.

Other Hardware

When the app is backgrounded, release any locks obtained on the hardware:

  • Bluetooth
  • Camera
  • Speaker, unless the app is a music app
  • Microphone

Battery Level and State-Aware Code

1
2
3
4
5
6
7
8
-(BOOL)shouldProceedWithMinLevel:(NSUInteger)minLevel {
    UIDevice *device = [UIDevice currentDevice];
    device.batteryMonitoringEnabled = YES;
UIDeviceBatteryState state = device.batteryState;
if(state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull) {
//Any operation can be performed during charging or when the device is fully charged. return YES;
}
//The batteryLevel returned by UIDevice is in the range 0.00–1.00. NSUInteger batteryLevel = (NSUInteger) (device.batteryLevel * 100); if(batteryLevel >= minLevel) { return YES;
} return NO;
}

CPU usage code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-(float)appCPUUsage { 
kern_return_t kr; task_info_data_t info; mach_msg_type_number_t infoCount = TASK_INFO_MAX;
kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)info, &infoCount); if (kr != KERN_SUCCESS) {
return -1; }
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count;
thread_basic_info_t basic_info_th;
kr = task_threads(mach_task_self(), &thread_list, &thread_count); if (kr != KERN_SUCCESS) {
return -1; } float tot_cpu = 0; int j;
for (j = 0; j < thread_count; j++) { thread_info_count = THREAD_INFO_MAX; kr = thread_info(thread_list[j], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS) { return -1;
}
basic_info_th = (thread_basic_info_t)thinfo;
if (!(basic_info_th->flags & TH_FLAGS_IDLE)) { tot_cpu += basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0; }
}
vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); return tot_cpu;
}

Alert the user if the battery level is low, and request the user’s per‐ mission to execute battery-intensive operations—only proceed if the user agrees.

Always use an indicator to show the progress of a long-running task, whether it is about computation being done on the device or merely downloading some content. Providing users with an esti‐ mate of how long the task will take to complete helps them decide whether it’s necessary to continue charging the device.

Best Practices

  • Minimize hardware use—in other words, start interaction with the hardware as late as possible and stop once the task is complete.
  • Check the battery level and charging status before starting intensive tasks.
  • If the battery level is low, prompt the user to determine whether the task should really be executed, and proceed only if the user agrees.
  • Alternatively, include a setting to let the user define a threshold battery level below which the app should prompt the user before executing intensive operations.

5重罪

  1. 自我 —— 没有以听众为中心 (内容太多)

  2. 迷茫 —— 目标不确定
    页面阅读型, 辅助演讲型

  3. 混乱 —— 思路不清晰,没有说服力

  4. 乏味 视觉设计单调乏味

  5. 粗糙 PPT不够精美专业

工作型PPT的评判要求

60

  • 观点鲜明
  • 逻辑清晰
  • 言之有物 (图片,案例,数据)

80

  • 图文并茂
  • 阅读友好

100

  • 细节完美
  • 整洁美观

素材网站

sc.chinaz.com/ppt

www.quanjing.com

www.taopic.com

sc.chinaz.com/tupian

www.iconfinder.com

www.iconspedia.com

www.easyicon.net

图片要求

  1. 高清无码
  2. 与内容相关
  3. 充满时代感
  4. 与正文协调一致

突然发现一个比较有意思的UI debug工具,所以接直接总结一把现在用到过的UI debug工具

Reveal

老牌工具,Xcode开发的时候可以在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions的debug断点中加入expr (Class)NSClassFromString(@"IBARevealLoader") == nil ? (void *)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2) : ((void*)0)可以不添加framework的情况下,在模拟器中debug,如果要真机debug那就要把reveal framework打包在项目中了。优点视图层次展现清晰,能即时修改属性并作用到UI上,缺点要钱,要钱,要钱

Flex

开源工具,需要集成在项目中,优点可以方便的修改UI的属性,缺点影响包体大小,多层次的UI界面点击选中比较困难

Xcode

在xcode开发的时候本身可以使用debug选项debug view hierarchy,优点是比较好的展现View的层次结构,但是只能随便看看并不能做任何的修改,就好比lite版的Reveal

UIDebuggingInformationOverlay

iOS自带私有的UI debug类,也是只能看,不过能调节view的透明度,具体功能稍后分析,优点系统自带,不占用任何包体大小,功能随版本更新,缺点私有API,不能带上线

今天具体的就是想说一下UIDebuggingInformationOverlay

UIDebuggingInformationOverlay 6大功能

View Hierarchy

选中Window的子view, 可以更换不同的window查看其子view

VC Hierarchy

查看存活的VC里的子view

Ivar Explorer

UIApplication中的所有变量

Measure

可以测量屏幕上点到点的距离, 打开View Mode可以测量任意UI元素的尺寸

Spec Compare

可以那设计图和当前的界面直接做对比,精准检验视觉还原度

System Color Audit

点击进入是空列表,不知道可以干啥

UIDebuggingInformationOverlay使用方法

1
2
3
4
Class overlayClass = NSClassFromString(@"UIDebuggingInformationOverlay");
[overlayClass performSelector:NSSelectorFromString(@"prepareDebuggingOverlay")];
id obj = [overlayClass performSelector:NSSelectorFromString(@"overlay")];
[obj performSelector:NSSelectorFromString(@"toggleVisibility")];

后面两行可以不用写,只要双指点击状态栏就可以了

##Anyway
不缺钱的首选还是Reveal,如果不舍得花钱,可以根据自身的情况选择其他工具

Why?

随着项目中源码越来越多,源码集成形式编译一次需要20+min。

How?

项目组件化后用Pod管理, 然后Pod里面维护的不是源代码,而是静态链接库,通过Cocoapods对zip支持这一特性,对项目进行二进制集成,将项目中所有的pod编译成二进制库(.a)文件。 这样最终编译的时候, 只需要进行部分编译加链接签名,完成打包,以此来加快编译速度。

Benefits

集成方式 编译时间(min)
源码集成 20+
二进制集成 5+

Details

  1. 创建一个 Xcode 工程
  2. 生成 Podfile,进行 Pod Install
  3. 生成静态链接库
  4. 解析原有 PodSpec,修改里面的 sourcecode 为 *.h 并添加静态链接库
  5. 发布到私有cocoapod repo上

创建一个 Xcode 工程

创建一个模板工程,丢到 Git 上

这里需要注意一个问题,工程里面一定要有个类,否则他不会编译的。

生成 Podfile,进行 Pod Install

创建Podfile模板

target 'Template' 
# 可替换内容
end

而可替换内容,应该是脚本的传入参数。例如 AFNetworking ~> 2.4

生成静态链接库

这里我们选择用 xcodebuild 来打包

但这里需要注意个问题。我们目标是生成静态链接库,所以我们要保证 i386、x86_64、armv7、arm64 四个平台都要有。

  • armv7 arm64
1
2
3
4
5
xcodebuild -workspace Template.xcworkspace \
-scheme Template \
-configuration Release \
build \

CONFIGURATION_BUILD_DIR=~/Desktop/workspace/Template/build

注意 CONFIGURATION_BUILD_DIR 需要绝对路径,相对路径会报找不到文件的错误。

  • i386 x86_64
1
2
3
4
5
6
7
8
xcodebuild -workspace Template.xcworkspace \
-scheme Template \
-configuration Release \
clean build \
CONFIGURATION_BUILD_DIR=~/Desktop/workspace/Template/build \
ARCHS='i386 x86_64' \
VALID_ARCHS='i386 x86_64'\
-sdk iphonesimulator

相对于 arm 平台,这个版本多了 clean, ARCHS, VALID_ARCHS, -sdk 这几个参数来保证编译顺利通过。

  • 合并 .a 文件
1
lipo -create libSignatureLibary_armv7.a libSignatureLibary_i368.a -output libSignatureLibary.a

解析原 podspec

把原有的podspec文件通过 Pod 的工具转换成 json。

pod ipc spec xxx.podspec > xxx.podespec.json

修改xxx.podespec.json内容

  1. s.source的更改,原来是指向该pod所对应的git仓库地址,现在则是指向该pod所对应文件服务器上zip包的地址。(这得益于Cocoapods对zip支持的这一特性)
  2. vendored_libraries的更改。vendored_libraries便是指向pod内部所使用的二进制包。
  3. 二进制初,在s.resource中并没有放指定.m文件,如果想要查看二进制之后pod的.m文件只能通过源码全量集成来看,后来为了能够在二进制中查看.m文件,遂把.m文件也放到了s.resource下面,这样既满足二进制,又能够查看响应的.m文件。
  4. 通过递归方法修改s.source_file.source_file中指向该pod的.h文件。
  5. 若该pod中还引用了子pod的相关文件,则要递归修改子pod相关问题。

在给podspec添加vendored_libraries的时候,有的pod自身已经引入了别的二进制包,所以事先要判断原来是否存在二进制包,并且原来的podspec所引入的二进制包可能不止一个。不然容易出现新增的二进制包将原来二进制包替换的情况。

发布

打包.a和.h成zip包, 上传到文件服务器,上传xxx.podespec.json到私有cocoapod repo

项目二进制集成

由于Podfile本身就是用ruby执行的文件,所以可以添加ruby的脚本在里面

  • 拉取远端二进制集成的spec.json文件到本地目录.cache_sdk_specs

  • 在Podfile中导入二进制脚本

require './binary_pod'

  • 对于需要二进制的Pod进行专属二进制pod的指定

binary_pod 'AFNetworking', '2.4.0'

  • 脚本生成专属的Podfile

pod 'AFNetworking', :podspec => './.cache_sdk_specs/AFNetworking/1.0.2.1/AFNetworking.podspec'

对于zip后的pod,在上传到文件服务器后,项目中通过pod install拉去二进制包的过程中,遇到过路径相关问题而无法拉取响应的.a文件。解决方案是在指定vendored_libraries的时候需要在原有路径前新增一层级路径,看过微信和微博的sdk都是在前面包了一层路径,所以仿效来,果然可行。具体原因不明

END

暂时收集了点文章,之后再重新整理一下

Reference

减少编译时间

离屏渲染优化详解

一键清除 objc 项目中的无用方法

iOS 保持界面流畅的技巧

相关github

https://github.com/ibireme/YYKit

https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

https://github.com/path/FastImageCache

https://github.com/ming1016/study


优化UITableViewCell高度计算的那些事

相关github

https://github.com/johnil/VVeboTableViewDemo

This is a good question and I wanted to answer it when I first saw it submitted way back in January — sorry I never got around to it!

The mechanism I’ve used is as simple as manually profiling your app (Time Profiler in Instruments). Then look for time on the main thread. Any time over about 10ms, especially if you know it will happen within a single runloop (e.g. call stack growing and then collapsing back to the base) will cause frame drops for scrolling, gestures, and physics-simulating animations.

The FPS measurement is not very useful because you may be holding a pretty high average FPS, but then drop a bunch of frames together; or only drop one frame per second, but it still feels bad. Basically FPS measurement does not correlate well with the human perception of the user experience.

Please feel free to post a topic on our Google Group for any further questions or discussion — I’d love to help! https://groups.google.com/forum/#!forum/asyncdisplaykit


iOS app性能优化的那些事(二)

Help! My tables don’t scroll smoothly!

(多帖总结) iOS性能优化技巧

iOS App 性能备忘

微信读书 iOS 性能优化总结

最近想给APP瘦身,号称包的大小也会影响用户量 = =, 所以立马去回顾一下WWDC 2015 Session 404,App Thinning in Xcode

AppThinning App由Slicing, On Demand Resources, Bitcode

Slicing

通过将app bundle资源根据不同的设备特性分为不同的版本。对于图片资源,会根据设备(如iphone6 plus需要@3x,iphone 6需要@2x)所需图片分辨率不同分发给对应设备所需对应的图片资源。

例如在6plus中,大屏储存需要3x的图片,所以会分发对应的图片资源

On Demand Resources

主要是加快下载速度,减小app的大小,改善第一次启动的体验。简而言之就是把资源放云端

Bitcode

是把指令集优化。根据你设备的状态去做编译优化,进而提升性能,对包的大小优化起不到什么本质上的作用


Slicing

苹果的文档如是说

In fact, app slicing handles the majority of the app thinning process. ‘App Slicing’ feature finally switched on in iOS 9.0.2

所以主要精力就放Slicing上吧

如何使用Slicing

对于Slicing的使用很简单只需要满足下面几个条件:

  • 当前项目的development target版本 iOS7及以上
  • 图片资源通过asset catalog进行管理
  • 设备版本必须是iOS9.0.2以后才会生效,以下的版本维持原样

APP通过asset catalog管理的图片在编译后,会在DPScope中生成Asset.car文件,该文件中包含了其管理的图片资源。App Slicing会对不同尺寸设备 编译之后的分发设备所需的@2x和@3x图片

在项目中现在图片主要分散在项目本身的Images.xcassets和各个业务Pod中, 针对现状如何使用Slicing有两种方案

  • 方案1

每个Pod单独建立各自的asset catalog,管理Pod自身的图片资源。这样在编译之后,会将项目中所有的asset catalog编译成一个Assets.car文件。
优点:每个pods单独管理自身的图片资源。
缺点:1.需要更改各个pod的podspec文件中的s.resource。指向该pod所建立的Asset.xcassets。2.不方便本地测试,只能通过发布到testflight测试。

  • 方案2

仍然通过App target中的Images.xcassets来管理整个项目中的图片资源。在pod install完毕后,将各个Pod中的图片资源放到Images.xcassets中来统一管理。(具体方法就是将图片拷贝到Images.xcassets中,生成对应图片名字的.imageset文件夹,文件夹下生成Contents.json文件来指定对应的1x,2x,3x版本图片用于分发)。
优点:各个pod不需要任何工作量
缺点:1.需要在pod install完毕后,删除Target Support Files中Pods-MyTarget-resources.sh中对应图片的 install_resource 行。不然编译会报错。2.通过脚本,在pod install完毕后,将Pods下的图片资源放到Images.xcassets中,并删除指定的shell脚本行数。

实际效果

设备类型 原始包大小 使用AppSlcing包大小 实际安装后大小
5s (@2x) 52.7M 35.4M 30.6M
6s plus(@3x) 52.7M 36.6M 31M

其他压缩图片减少包的方法

  1. tinypny压缩png图片
  2. 使用第三方工具image_optim压缩图片
  3. 使用xcode自带的pngcrush压缩图片

结论 2,3 然并卵 = =

踩过的坑

  1. 通过方案1进行调研的时候,虽然xcode会将项目中的xcassets和各个pod中的xcassets管理的图片打包成一个Asset.car文件,但是通过查看DerivedData中的Asset.car文件,发现图片并没有分发,最后发现原来是在pod install的时候,在Pods-MyTarget-resources.sh中影响了自动分发的功能,所以只能通过发布testflight来进行测试。测试发现,是能够自动分发图片的,只是本地无法测试。

  2. 通过方案2进行调研的时候,原以为只需要删除已经移除图片所对应的install_resource 行即可,但是发现仍有报错。一些的podspec中,对资源的使用通过s.resouce_bundle的形式使用的,而其余的pod的podspec对资源的使用是通过s.resouce形式引用的。所以在脚本中需要对上面的几个pod的图片暂时进行了剔除

最近买了一把samsung p728的智能锁,所以想着怎么把这把锁给玩转了

这把锁提供多种开锁方式,密码,磁卡,指纹,蓝牙,还有基本不会用的物理钥匙。

现在nfc基本是手机的标配了,还有各种pay,小米更接地气的支持了交通卡,所以第一个切入点必然是磁卡拉

Step One - 收集信息

p728基本可以使用一般的磁卡,nfc卡贴作为进门卡贴,由此可以猜想,这个安全级别也就是读取了个卡的ID(真心为家里的放招财猫里几百块钱硬币担忧)
找了个比较nb的android app - TagInfo来读取一下磁卡的信息,这个app功能还是很全的,有兴趣的人可以深入把玩一下。

卡的ID为:44:CB:9F:F4 (当然是随便找了张卡读的,怎么可能告诉你们真实的ID,关系到我们家几百大洋呢)

Step Two - 写入手机

出于安全考虑,android手机NFC的ID是一个4个字节长度的随机id,每次连接都会变化,并且都以“80”开头,但是这个其实是可以固定的

  • 到手机的 /etc/ 目录找一个文件名为libnfc-brcm-20791b05.conf,默认情况下,文件中NFA_DM_START_UP_CFG 的配置项是这样的值:

    {49:CB:01:09:A5:01:01:CA:1E:00:00:00:00:06:00:00:00:00:0F:00:00:00:00:20:A1:07:00:14:01:00:00:10:98:3A:00:00:00:00:02:B5:03:01:02:FF:80:01:01:C9:04:03:0F:AB:00:5B:01:00:B2:04:A8:0B:00:00:CF:02:02:08:B1:06:00:20:00:00:00:12:33:04:A0:21:F5:91}

  • 通过修改这个值就可以固定ID。手机上直接装一个Root Explorer,打开文件,找到相应的配置项,最后写入33作为标志位,接着接上要指定的ID长度,在当前的情况下就是04,最后在后面接上要制定的ID:44:CB:9F:F4,接着改变最首的数字,加上我们总共增加的字符串长度,这里我们需要加上6,如果不会16进制加减,使用mac的计算器,切换成高级,点击16进制,直接加就行了, 所以最后配置项变成:

    {4F:CB:01:09:A5:01:01:CA:1E:00:00:00:00:06:00:00:00:00:0F:00:00:00:00:20:A1:07:00:14:01:00:00:10:98:3A:00:00:00:00:02:B5:03:01:02:FF:80:01:01:C9:04:03:0F:AB:00:5B:01:00:B2:04:A8:0B:00:00:CF:02:02:08:B1:06:00:20:00:00:00:12:33:04:A0:21:F5:91:33:04:44:CB:9F:F4}

  • 保存并退出,Root Explorer自动会生成back up文件,简单方便

Step Three - 测试

点亮屏幕把手机靠近磁卡感应区,叮的一声,成功!!!

是非能在运行时创建的类中添加实例变量?

运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

autoreleasepool如何实现

autoreleasepool 以一个队列数组的形式实现,主要通过下列三个函数完成.

  1. objc_autoreleasepoolPush
  2. objc_autoreleasepoolPop
  3. objc_autorelease

看函数名就可以知道,对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。

objc_msgSend(receiver, selector);

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE

isa的指针指向他的类对象,从而可以找到对象上的方法

[obj foo]在编译之后为((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName("foo")); 即: objc_msgSend(obj, @selector(foo))

什么时候会报unrecognized selector的异常?

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会:

Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程,如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发(Message Forwarding)。

Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

对象的内存销毁时间表,分四个步骤:

  1. 调用 -release :引用计数变为零
    • 对象正在被销毁,生命周期即将结束.
    • 不能再有新的 __weak 弱引用, 否则将指向 nil.
    • 调用 [self dealloc]
  2. 父类 调用 -dealloc
    • 继承关系中最底层的父类 在调用 -dealloc
    • 如果是 MRC 代码 则会手动释放实例变量们(iVars)
    • 继承关系中每一层的父类 都在调用 -dealloc
  3. NSObject 调 -dealloc
    • 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
  4. 调用 object_dispose()
    • 为 C++ 的实例变量们(iVars)调用 destructors
    • 为 ARC 状态下的 实例变量们(iVars) 调用 -release
    • 解除所有使用 runtime Associate方法关联的对象
    • 解除所有 __weak 引用
    • 调用 free()

objc_msgForward消息转发做的几件事:

  1. 调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。

  2. 调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 self ,否则会形成死循环。

  3. 调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。

  4. 调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非ni。

  5. 调用doesNotRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

#TO BE CONTINUED