25 iOS App Performance Tips & Tricks
This article gathers together 25 tips and tricks that you can use to improve the performance of your apps, in the form of a handy checklist. By .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
25 iOS App Performance Tips & Tricks
45 mins
Intermediate Performance Improvements
Okay, so you’re pretty confident that you’ve hit all of the low-hanging fruit when it comes to optimizing your code. But sometimes there are solutions that aren’t quite as obvious, and depend heavily on how you structure and code your app. However, in the right context, they can be invaluable!
More views means more drawing; which ultimately means more CPU and memory overhead. This is especially true if your app embeds many views inside of a UIScrollView.
The trick to managing this is to mimic the behavior of UITableView and UICollectionView: don’t create all subviews at once. Instead, create your views as you need them, adding them to a reuse queue when you’re finished with them.
This way, you have only to configure your views when a scroll is performed, avoiding the allocation cost — which can be expensive.
The problem of timing the creation of views applies to other areas of your app as well. Take the situation where you need to present a view when the user taps a button. There are at least two approaches to this:
- Create the view when the screen is first loaded and hide it; then when you need it, show it.
- Do nothing until you need to show the view. Then, create the view and show it, all at once.
Each approach has its own pros and cons.
Using the first method, you consume more memory because you immediately create the view which holds on to that memory until it’s released. However, when the user does taps the button, your app will appear more responsive as it only needs to change the view’s visibility.
Taking the second approach will have the opposite effect; by creating the view only when it’s required, you consume less memory; however, the app won’t appear as responsive when the button is tapped.
A great rule of thumb when developing your app is to “cache what matters” — that is, things that are unlikely to change, but are accessed frequently.
What can you cache? Some candidates for caching are remote server responses, images, or even calculated values, such as UITableView row heights.
NSURLConnection already caches resources on disk or in memory according to the HTTP headers it processes. You can even create a NSURLRequest manually and make it load only cached values.
Here’s a great snippet to use whenever you need to create a NSURLRequest for an image that is unlikely to change:
+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
request.HTTPShouldHandleCookies = NO;
request.HTTPShouldUsePipelining = YES;
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
return request;
}
Note that you can fetch a URL request via NSURLConnection, but AFNetworking can fetch it as well; that way you don’t need to change all your networking code because of this tip! :]
If want to know more about HTTP caching, NSURLCache, NSURLConnection and friends, be sure to take a look at the NSURLCache entry on NSHipster.
If you need to cache other things that don’t involve HTTP requests, then NSCache is your go-to guy.
NSCache looks and behaves like an NSDictionary, but it automatically removes its contents when the system needs to reclaim memory. Mattt Thompson wrote this amazing post on NSHipster about it.
Interested in learning more about HTTP caching? Google has a best-practices document on HTTP caching that is a recommended read.
There are several ways to make great-looking buttons in iOS. You can use full sized images, resizable images, or you could go the distance and draw it manually using CALayer, CoreGraphics or even OpenGL.
Of course, there’s different levels of complexity with each of these approaches, as well as differences in their performance. There’s an awesome post about iOS graphics performance here which is well worth a read. Andy Matuschak, who is a member of the UIKit team over at Apple, commented on the post, and there’s some great insight into the different approaches and their performance trade-offs.
The short story is that using pre-rendered images is faster, because iOS doesn’t have to create an image and draw shapes on it to finally draw into than screen (the image is already created!). The problem is that you need to put all those images in your app’s bundle, increasing its size. That’s why using resizable images is so great: you save space by removing “wasted” image space that iOS can repeat for you. You also don’t need to generate different images for different elements (e.g. buttons).
However, by using images you lose the ability to tweak your images by code, needing to regenerate them every and putting them into the app again and again. That can be a slow process. Another point is that if you have an animation or just a lot of images with slightly changes (they can have multiple overlay color, for example), you’ll have to embed a lot of images, growing the app’s bundle size.
To summarize, you need to think what’s most important to you: drawing performance or app’s size. Generally both are important, so you’ll use both approaches in the same project!
iOS notifies all running apps when system memory is running low. Here’s what the official Apple documentation says about handling the low memory warning:
If your app receives this warning, it must free up as much memory as possible. The best way to do this is to remove strong references to caches, image objects, and other data objects that can be recreated later.
If your app receives this warning, it must free up as much memory as possible. The best way to do this is to remove strong references to caches, image objects, and other data objects that can be recreated later.
Fortunately, UIKit provides several ways to receive these low-memory warnings, including the following:
- Implement the applicationDidReceiveMemoryWarning: method of your app delegate.
- Override didReceiveMemoryWarning in your custom UIViewController subclass.
- Register to receive the UIApplicationDidReceiveMemoryWarningNotification notification.
Upon receiving any of these warnings, your handler method should respond by immediately freeing up any unnecessary memory.
For example, the default behavior of UIViewController is to purge its view if that view is not currently visible; subclasses can supplement the default behavior of their parent class by purging additional data structures. An app that maintains a cache of images might respond by releasing any images that are not currently on-screen.
It’s very important to release all memory possible once you receive a memory warning. Otherwise, you run the risk of having your app killed by the system.
However, be careful when you start culling objects to free up memory, as you’ll need to make sure they can be recreated later. Be sure to use the Simulate memory warning feature on the iOS simulator to test this condition while you are developing your app!
Some objects are very slow to initialize — NSDateFormatter and NSCalendar are two examples. However, you can’t always avoid using them, such as when parsing dates from a JSON/XML response.
To avoid performance bottlenecks when working with these objects, try to reuse these objects if at all possible. You can do this by either adding a property to your class, or by creating a static variable.
Note that if you choose the second approach, the object will remain in memory while your app is running, much like a singleton.
The code below demonstrates making a property that lazy-loads a date formatter. The first time it is called, it creates a new date formatter. Future calls will just return the already created instance:
// in your .h or inside a class extension
@property (nonatomic, strong) NSDateFormatter *formatter;
// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
if (! _formatter) {
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
}
return _formatter;
}
Update 13/4/13: As our reader Aaron pointed out, there’s a problem with this approach if this method is being called from multiple threads. If that’s your case and you’re fine with having one NSDateFormatter shared with all instances of your class, you can do the following:
// no property is required anymore. The following code goes inside the implementation (.m)
- (NSDateFormatter *)formatter {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
});
return formatter;
}
As our reader said, “this will ensure it’s initialized only once, and if a second thread calls the method while the block is running, it will pause until the block has completed”.
Also remember that setting a NSDateFormatter’s date format is almost as slow as creating a new one! Therefore, if you frequently need to deal with multiple date formats in your app, your code may benefit from initially creating, and reusing, multiple NSDateFormatter objects.
So you’re a game developer? Then sprite sheets are one of your best friends. Sprite sheets make drawing faster and can even consume less memory than standard screen drawing methods.
There are two awesome sprite sheet tutorials about sprite sheets on this site:
- How To Use Animations and Sprite Sheets in Cocos2D
- How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats
The second tutorial covers pixel formats in detail, which can have a measurable impact on a game’s performance.
If you’re not yet familar with sprite sheets, then a great introduction can be found in SpriteSheets – The Movie, Part 1 and Part 2. The author of these videos is Andreas Löw, the creator of Texture Packer, one of the most popular tools for creating sprite sheets.
Besides using sprite sheets, several tips already covered here can be applied to games as well. For example, if your game has many sprites, such as enemies or projectiles in your standard shoot-em-up, then you can reuse sprites instead of recreating them every time.
Many apps make calls for data from remote servers to get information the app requires to function. This data usually comes across in JSON or XML format. It’s important to try and use the same data structure at both ends when requesting and receiving the data.
Why? Manipulating data in memory to fit your data structures can be expensive.
For example, if you need to display the data in a table view, it would be best to request and receive the data in an array format to avoid any intermediate manipulation of the data to make it fit the data structure that you’re using in your app.
Similarly, if your application depends on accessing specific values by their keys, then you’ll probably want to request and receive a key/value pair dictionary.
By getting the data in the right format the first time, you’ll avoid a lot of re-processing in your app to make the data fit your chosen structure.
16) Choose the Right Data Format
There are multiple ways you can transfer data to your app from a web service, but the most common two are JSON and XML. You want to make sure you choose the right one for your app.
JSON is faster to parse, and is generally smaller than XML, which means less data to transfer. And since iOS 5, there’s built-in JSON deserialization so it’s easy to use as well.
However, one advantage XML has is that if you use the SAX parsing method, you can work with XML data as you read it off the wire, instead of having to wait for the entire data to arrive before you parse it like JSON. This can give you increased performance and reduced memory consumption when you are dealing with very large sets of data.
17) Set Background Images Appropriately
Like many other things in iOS coding, there’s at least two different ways to place a background image on your view:
- You can set your view’s background color to a color created with UIColor’s colorWithPatternImage.
- You can add a UIImageView subview to the view.
If you have a full size background image, then you should definitely use a UIImageView because UIColor’s colorWithPatternImage was made to create small pattern images that will be repeated, and not large images size. Using UIImageView will save a lot of memory in this case.
// You could also achieve the same result in Interface Builder
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];
However, if you plan to have a patterned image background, which uses smaller images which will be repeated or tiled to fill the background, you should go with UIColor’s colorWithPatternImage instead, as it’s faster to draw and won’t use a lot of memory in this case.
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
UIWebView is very useful. It’s a very easy to display web content, or even to create a visual aspect of your app that would be difficult with standard UIKit controls.
However, you may notice that the UIWebView component you can use in your apps is not as fast as the one which powers Apple’s Safari app. This is down to the restricted use of Webkit’s Nitro Engine, which features JIT compilation.
So to get the best performance, you’ll need to tweak your HTML a bit. The first thing you should do is get rid of as much Javascript as you can, which includes avoiding large frameworks such as jQuery. It can sometimes be a lot faster to work with vanilla Javascript instead of relying on frameworks to do the work for you.
Also follow the practice of loading your Javascript files asynchronously where possible – especially when they don’t directly impact the behavior of the page, such as analytics scripts.
And finally — always be aware of the images that you are using, and keep images right-sized for your purposes. As mentioned earlier in this tutorial, make use of sprite sheets wherever possible to conserve memory and improve speed.
For more information, be sure to take a look at WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS.
So you need to add a shadow to a view, or to a layer. How should you handle this?
Most developers would just add the QuartzCore framework to their project, and then add the following code:
#import <QuartzCore/QuartzCore.h>
// Somewhere later ...
UIView *view = [[UIView alloc] init];
// Setup the shadow ...
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity = 0.6;
Looks pretty easy, right?
The bad news is that there’s a problem with this approach. Core Animation has to do an offscreen pass to first determine the exact shape of your view before it can render the drop shadow, which is a fairly expensive operation.
The good news is that there’s an alternative that is much easier for the system to render: setting the shadow path!
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
By setting the shadow path, iOS won’t need to recalculate how it should draw the shadow all the time. Instead it’s going to use a pre-calculated path that you’re informing. The bad news is that depending on your view format, it may be harder to calculate the path by your own. Another problem is that you need to update the shadow path every time your view’s frame changes.
If you want to know more about this trick, Mark Pospesel wrote a great post about shadowPath.
Table views need to scroll quickly — when they don’t, users really notice the lag.
To keep your table views scrolling smoothly, ensure that you’ve implemented all of the suggestions below:
- Reuse cells by setting the correct reuseIdentifier.
- Make as many views opaque as possible, including the cell itself.
- Avoid gradients, image scaling, and offscreen drawing.
- Cache the height of any rows if they aren’t always the same.
- If the cell shows content that comes from the web, be sure to make those calls asynchronously and cache the responses.
- Use shadowPath to set up shadows.
- Reduce the number of subviews.
- Do as little work as possible in cellForRowAtIndexPath:. If you need to do some work, do it only once and cache the results.
- Use the appropriate data structure to hold the information you need. Different structures have different costs for different operations.
- Use rowHeight, sectionFooterHeight and sectionHeaderHeight to set constant heights instead of asking the delegate.
21) Choose Correct Data Storage Option
What are your options when it comes to storing and reading large data sets?
You have several options, including:
- Store them using NSUserDefaults
- Save to a structured file in XML, JSON, or Plist format
- Archive using NSCoding
- Save to a local SQL database such as SQLite
- Use Core Data.
What’s the issue with NSUserDefaults? Although NSUserDefaults is nice and easy, it’s really only good if you have a very small amount of data to save (like what level you’re on, or whether sound is turned on and off). Once you start getting large amounts of data, other options are better.
Saving to a structured file can be problematic as well. Generally, you need to load the entire file into memory before you can parse it, which is an expensive operation. You could use SAX to process a XML file, but that’s a complex solution. As well, you’d end up having all objects loaded in memory — whether you want them there or not.
Okay, then, what about NSCoding? Unfortunately, it also reads and writes to a file, so it experiences the same problems as above.
Your best bet in this situation is to use SQLite or Core Data. By using these technologies, you can perform specific queries to only load the objects you need and avoid a brute-force searching approach to retrieve the data. In terms of performance, SQLite and Core Data are very similar.
The big difference between SQLite and Core Data is really about the general usage of each. Core Data represents an object graph model, while SQLite is just a regular DBMS. Usually Apple recommends that you go with Core Data, but if you have a particular reason you want to avoid it, you can go lower level to SQLite.
If you choose to use SQLite in your app, a handy library to use is FMDB which allows you to work with a SQLite database without having to delve into the SQLite C API.