├── Foundation.md ├── QuartzCore.md ├── README.md └── UIKit.md /Foundation.md: -------------------------------------------------------------------------------- 1 | # Foundation 2 | 3 | - [NSDateFormatter](#nsdateformatter) 4 | - [NSFileManager](#nsfilemanager) 5 | - [NSObjCRuntime](#nsobjcruntime) 6 | - [NSString](#nsstring) 7 | 8 | --- 9 | 10 | ### NSDateFormatter 11 | 12 | `NSDateFormatter` is not the only class that is expensive to setup, but it is expensive and utilized enough that Apple specifically recommends caching and reusing instances where possible. 13 | 14 | > Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, it is typically more efficient to cache a single instance than to create and dispose of multiple instances. One approach is to use a static variable. 15 | 16 | [Source](https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html) 17 | 18 | A common method of caching `NSDateFormatter`s is to use `-[NSThread threadDictionary]` (because `NSDateFormatter` is not thread-safe): 19 | 20 | ```objective-c 21 | + (NSDateFormatter *)cachedDateFormatter { 22 | NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; 23 | NSDateFormatter *dateFormatter = [threadDictionary objectForKey:@"cachedDateFormatter"]; 24 | if (dateFormatter == nil) { 25 | dateFormatter = [[NSDateFormatter alloc] init]; 26 | [dateFormatter setLocale:[NSLocale currentLocale]]; 27 | [dateFormatter setDateFormat: @"YYYY-MM-dd HH:mm:ss"]; 28 | [threadDictionary setObject:dateFormatter forKey:@"cachedDateFormatter"]; 29 | } 30 | return dateFormatter; 31 | } 32 | ``` 33 | 34 | #####- (NSDate *)dateFromString:(NSString *)string 35 | 36 | This is potentially the most common iOS performance bottleneck. As a result, much effort has been dedicated to discovering alternatives. Below are the best known `NSDateFormatter` substitutions for [ISO8601](http://en.wikipedia.org/wiki/ISO_8601) to `NSDate` conversion. 37 | 38 | ######strptime 39 | 40 | ```objective-c 41 | //#include 42 | 43 | time_t t; 44 | struct tm tm; 45 | strptime([iso8601String cStringUsingEncoding:NSUTF8StringEncoding], "%Y-%m-%dT%H:%M:%S%z", &tm); 46 | tm.tm_isdst = -1; 47 | t = mktime(&tm); 48 | [NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]]; 49 | ``` 50 | 51 | [Source](http://sam.roon.io/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments) 52 | 53 | ######sqlite3 54 | 55 | ```objective-c 56 | //#import "sqlite3.h" 57 | 58 | sqlite3 *db = NULL; 59 | sqlite3_open(":memory:", &db); 60 | sqlite3_stmt *statement = NULL; 61 | sqlite3_prepare_v2(db, "SELECT strftime('%s', ?);", -1, &statement, NULL); 62 | sqlite3_bind_text(statement, 1, [iso8601String UTF8String], -1, SQLITE_STATIC); 63 | sqlite3_step(statement); 64 | int64_t value = sqlite3_column_int64(statement, 0); 65 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:value]; 66 | sqlite3_clear_bindings(statement); 67 | sqlite3_reset(statement); 68 | ``` 69 | 70 | [Source](http://vombat.tumblr.com/post/60530544401/date-parsing-performance-on-ios-nsdateformatter-vs) 71 | 72 | 73 | ### NSFileManager 74 | 75 | #####- (NSDictionary *)attributesOfItemAtPath:(NSString *)filePath error:(NSError *)error 76 | 77 | When attempting to retrieve an attribute of a file on disk, using `–[NSFileManager attributesOfItemAtPath:error:]` will expend an excessive amount of time fetching additional attributes of the file that you may not need. Instead of using `NSFileManager`, you can directly query the file properties using `stat`: 78 | 79 | ```objective-c 80 | //#import 81 | 82 | struct stat statbuf; 83 | const char *cpath = [filePath fileSystemRepresentation]; 84 | if (cpath && stat(cpath, &statbuf) == 0) { 85 | NSNumber *fileSize = [NSNumber numberWithUnsignedLongLong:statbuf.st_size]; 86 | NSDate *modificationDate = [NSDate dateWithTimeIntervalSince1970:statbuf.st_mtime]; 87 | NSDate *creationDate = [NSDate dateWithTimeIntervalSince1970:statbuf.st_ctime]; 88 | // etc 89 | } 90 | ``` 91 | 92 | ### NSObjCRuntime 93 | 94 | #####NSLog(NSString *format, ...) 95 | 96 | `NSLog()` writes messages to the Apple System Log facility. Written messages are presented in the debugger console when built and run via Xcode, in addition to the device's console log even in production. Additionally, `NSLog()` statements are serialized by the system and performed on the main thread. Even on fairly new iOS hardware, `NSLog()` takes a non-negligible amount of time while only providing debug value. As a result, it is recommended to use `NSLog()` as sparingly as possible in production. 97 | 98 | > Calling `NSLog` makes a new calendar for each line logged. Avoid calling `NSLog` excessively. 99 | 100 | [Source](https://developer.apple.com/videos/wwdc/2012/?id=235) 101 | 102 | The following are commonly used log definitions that are used to selectively perform `NSLog()` in debug/production: 103 | 104 | ```objective-c 105 | #ifdef DEBUG 106 | // Only log when attached to the debugger 107 | # define DLog(...) NSLog(__VA_ARGS__) 108 | #else 109 | # define DLog(...) /* */ 110 | #endif 111 | // Always log, even in production 112 | #define ALog(...) NSLog(__VA_ARGS__) 113 | ``` 114 | 115 | [Source](http://iphoneincubator.com/blog/debugging/the-evolution-of-a-replacement-for-nslog) 116 | 117 | ### NSString 118 | 119 | #####+ (instancetype)stringWithFormat:(NSString *)format,, ... 120 | 121 | `NSString` creation is not particularly expensive, but when used in a tight loop (as dictionary keys, for example), `+[NSString stringWithFormat:]` performance can be improved dramatically by being replaced with `asprintf` or similar functions in C. 122 | 123 | ```objective-c 124 | NSString *firstName = @"Daniel"; 125 | NSString *lastName = @"Amitay"; 126 | char *buffer; 127 | asprintf(&buffer, "Full name: %s %s", [firstName UTF8String], [lastName UTF8String]); 128 | NSString *fullName = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; 129 | free(buffer); 130 | ``` 131 | 132 | #####- (instancetype)initWithFormat:(NSString *)format, ... 133 | 134 | See [`+[NSString stringWithFormat:]`](#-instancetypestringwithformatnsstring-format-) 135 | -------------------------------------------------------------------------------- /QuartzCore.md: -------------------------------------------------------------------------------- 1 | # QuartzCore 2 | 3 | - [CALayer](#calayer) 4 | 5 | --- 6 | 7 | ### CALayer 8 | 9 | #####@property BOOL allowsGroupOpacity 10 | 11 | As of iOS7, this property indicates whether or not the layer's sublayers inherit the layer's opacity. The primary use case for this functionality is when animating a layer's transparency (causing the opacity of subviews to be visible). However, if this rendering style is not needed for your use case, it can be disabled to improve performance. 12 | 13 | > When true, and the layer's opacity property is less than one, the layer is allowed to composite itself as a group separate from its parent. This gives the correct results when the layer contains multiple opaque components, but may reduce performance. 14 | > 15 | > The default value of the property is read from the boolean UIViewGroupOpacity property in the main bundle's Info.plist. If no value is found in the Info.plist the default value is YES for applications linked against the iOS 7 SDK or later and NO for applications linked against an earlier SDK. 16 | 17 | Source unavailable at this time. See `CALayer.h` in Xcode. 18 | 19 | > (Default on iOS 7 and later) Inherit the opacity of the superlayer. This option allows for more sophisticated rendering in the simulator but can have a noticeable impact on performance. 20 | 21 | [Source](https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html#//apple_ref/doc/uid/TP40009252-SW6) 22 | 23 | #####@property BOOL drawsAsynchronously 24 | 25 | The `drawsAsynchronously` property causes the layer's `CGContext` to defer drawing to a background thread. This property will provide the greatest benefit when enabled on layers that are redrawn frequently. 26 | 27 | > When this property is set to YES, the graphics context used to draw the layer’s contents queues drawing commands and executes them on a background thread rather than executing them synchronously. Performing these commands asynchronously can improve performance in some apps. However, you should always measure the actual performance benefits before enabling this capability. 28 | 29 | [Source](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html#//apple_ref/occ/instp/CALayer/drawsAsynchronously) 30 | 31 | > Any drawing that you do in your delegate’s drawLayer:inContext: method or your view’s drawRect: method normally occurs synchronously on your app’s main thread. In some situations, though, drawing your content synchronously might not offer the best performance. If you notice that your animations are not performing well, you might try enabling the drawsAsynchronously property on your layer to move those operations to a background thread. If you do so, make sure your drawing code is thread safe. 32 | 33 | [Source](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/ImprovingAnimationPerformance/ImprovingAnimationPerformance.html) 34 | 35 | #####@property CGPathRef shadowPath 36 | 37 | When applying shadow properties to a `CALayer`, it is recommended to set the `shadowPath` of the layer so as to allow the system to cache the shadow and reduce the amount of drawing necessary. When modifying the layer's bounds, the `shadowPath` should be re-set. 38 | 39 | ```objective-c 40 | CALayer *layer = view.layer; 41 | layer.shadowOpacity = 0.5f; 42 | layer.shadowRadius = 10.0f; 43 | layer.shadowOffset = CGSizeMake(0.0f, 10.0f); 44 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:layer.bounds]; 45 | layer.shadowPath = bezierPath.CGPath; 46 | ``` 47 | 48 | > Letting Core Animation determine the shape of a shadow can be expensive and impact your app’s performance. Rather than letting Core Animation determine the shape of the shadow, specify the shadow shape explicitly using the shadowPath property of CALayer. When you specify a path object for this property, Core Animation uses that shape to draw and cache the shadow effect. For layers whose shape never changes or rarely changes, this greatly improves performance by reducing the amount of rendering done by Core Animation. 49 | 50 | [Source](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/ImprovingAnimationPerformance/ImprovingAnimationPerformance.html) 51 | 52 | #####@property BOOL shouldRasterize 53 | 54 | Setting a `CALayer`'s `shouldRasterize` property to `YES` can improve performance for layers that need to be drawn only once. This layer can still be moved, scaled, and transformed. If a layer needs to be redrawn often, then setting `shouldRasterize` to `YES` can actually hurt drawing performance, because the system will attempt to rasterize the layer after each draw. 55 | 56 | > When the value of this property is YES, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content. Shadow effects and any filters in the filters property are rasterized and included in the bitmap. 57 | 58 | [Source](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html#//apple_ref/occ/instp/CALayer/shouldRasterize) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS App Performance Cheatsheet 2 | 3 | **A collection of code substitutions and configurations that can improve the performance of Objective-C code on iOS.** 4 | 5 | Most code substitutions and configurations documented here consist of replacing high-level APIs that were made for flexibility over performance with lower-level alternatives for the task at hand, or class properties that affect drawing performance. Good architecture and proper threading is always important for app performance, but sometimes we need to use specialized implementations. 6 | 7 | ### Table of Contents 8 | 9 | **iOS App Performance Cheatsheet** is sectioned into Markdown files by Objective-C framework: 10 | - [**Foundation**](Foundation.md) 11 | - [NSDateFormatter](Foundation.md#nsdateformatter) 12 | - [NSFileManager](Foundation.md#nsfilemanager) 13 | - [NSObjCRuntime](Foundation.md#nsobjcruntime) 14 | - [NSString](Foundation.md#nsstring) 15 | - [**UIKit**](UIKit.md) 16 | - [UIImage](UIKit.md#uiimage) 17 | - [UIView](UIKit.md#uiview) 18 | - [**QuartzCore**](QuartzCore.md) 19 | - [CALayer](QuartzCore.md#calayer) 20 | 21 | ### Why is this on GitHub? 22 | 23 | - Great format for saving to disk or accessing via the web 24 | - Encourages contributions and improvements 25 | - [GitHub Flavored Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) looks nice 26 | 27 | ### Disclaimer 28 | 29 | There is a reason why this is called a *cheatsheet*. You should avoid premature optimizations, and only seek out code replacements when you have determined that a specific code path has become a performance bottleneck. Hopefully, however, this cheatsheet will provide a bit of insight into some of the bottlenecks that usually arise, and some of the options available to you. 30 | 31 | ### Contributions 32 | 33 | Pull requests are welcome! The best contributions will consist of substitutions or configurations for classes/methods known to block the main thread during a typical app lifecycle. 34 | 35 | ### Contact 36 | 37 | - hello@danielamitay.com 38 | - http://www.danielamitay.com 39 | -------------------------------------------------------------------------------- /UIKit.md: -------------------------------------------------------------------------------- 1 | # UIKit 2 | 3 | - [UIImage](#uiimage) 4 | - [UIView](#uiview) 5 | 6 | --- 7 | 8 | ### UIImage 9 | 10 | #####+ (UIImage *)imageNamed:(NSString *)fileName 11 | 12 | If displaying a bundle image only once, it is recommended to use `+ (UIImage *)imageWithContentsOfFile:(NSString *)path` instead so as not to have the system cache the image. Per Apple documentation: 13 | 14 | > If you have an image file that will only be displayed once and wish to ensure that it does not get added to the system’s cache, you should instead create your image using imageWithContentsOfFile:. This will keep your single-use image out of the system image cache, potentially improving the memory use characteristics of your app. 15 | 16 | [Source](https://developer.apple.com/library/ios/documentation/uikit/reference/UIImage_Class/Reference/Reference.html#//apple_ref/occ/clm/UIImage/imageNamed:) 17 | 18 | ### UIView 19 | 20 | #####@property(nonatomic) BOOL clearsContextBeforeDrawing 21 | 22 | Setting a `UIView`'s `clearsContextBeforeDrawing` to `NO` can in many cases improve drawing performance, particularly if you are implementing the drawing code of a custom view. 23 | 24 | > If you set the value of this property to NO, you are responsible for ensuring the contents of the view are drawn properly in your drawRect: method. If your drawing code is already heavily optimized, setting this property is NO can improve performance, especially during scrolling when only a portion of the view might need to be redrawn. 25 | 26 | [Source](https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html#//apple_ref/occ/instp/UIView/clearsContextBeforeDrawing) 27 | 28 | > By default, UIKit clears a view’s current context buffer prior to calling its drawRect: method to update that same area. If you are responding to scrolling events in your view, clearing this region repeatedly during scrolling updates can be expensive. To disable the behavior, you can change the value in the clearsContextBeforeDrawing property to NO. 29 | 30 | [Source](https://developer.apple.com/library/ios/documentation/2ddrawing/conceptual/drawingprintingios/DrawingTips/DrawingTips.html) 31 | 32 | #####@property(nonatomic) CGRect frame 33 | 34 | When setting the frame property of a `UIView` item, it is valuable to ensure that the coordinates correspond to pixel locations, otherwise anti-aliasing will occur, reducing performance and potentially causing blurry edges in your interface. One straightforward solution is to use `CGRectIntegral()` to automatically round the `CGRect` values to the nearest point. For screens with a higher pixel density than 1 pixel per point, the coordinates only need to be rounded to the nearest `1.0f / screen.scale` 35 | --------------------------------------------------------------------------------