Tips for improving performance of your iOS application

Any iOS application worthy of a spot on their user’s home screen is made of 3 key ingredients: a great idea, stunning design and smooth performance. In a previous post, we shared a few guidelines to make your app look pretty. Today, we have some simple tips on how to improve the performance of your iOS application. At Pulse, we obsess over every small hiccup in the application and spend countless nights staring at Instruments at the end of our release cycles. Here are some of our insights that might help you in your development process.

Downsize your image assets

Apps with good visual design always delight users. To achieve pixel perfect graphics, every iOS application ships with several image assets. It is crucial that these images are as small in size as possible. Let me elaborate with an example.

It is common practice to add a button to a nib file and set its background to point to an image. When the nib file is read from disk, iOS instantiates all the individual objects in the file, including that button. When it notices that the button’s background points to an image, it reads the image from disk, inflates it in memory and renders it as the background. The bigger the image, the slower it is to read it from disk. Since all this happens synchronously on the main thread, it slows down the app. Tip #1: Once you are satisfied with an asset, remember to always compress it to the smallest size possible, without any loss in quality, before adding it to the bundle. As a rule of thumb, I have always been able to compress icons down to at most 4kb on disk. Check out Core Animation in Practice, Part 2 from WWDC 2010 for more info on optimizing graphics on screen.

Defer main thread operations

It goes without saying that any task that doesn’t need to be executed on the main thread should be shipped to a background thread. NSOperationQueues or Grand Central Dispatch are two great tools for such tasks. With tasks running on the main thread, you need to be very careful that they don’t interfere with a user’s touches. Such tasks can be roughly classified into two groups:

  • View Updates: Any changes to your views need to happen on the main thread. iOS makes it very easy to defer these changes by the simple, do not call us, we’ll call you rule – Never call drawRect yourself. Just call setNeedsDisplay and iOS will re-render your view when the user has stopped scrolling.
  • Processing: There are some critical processing tasks that cannot be performed on a background thread, like saving a Core Data database, changing in-memory state, etc. Tip #2: Group such tasks into independent chunks and execute them in the Default Runloop mode. Eg:
[self performSelectorOnMainThread:@selector(processDataOnMainThread:)
withObject:dictionaryOfParameters
waitUntillDone:NO
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]

When the user starts scrolling a scrollview or a tableview, the run loop mode is set to the Common modes. When the user stops scrolling, it is reset to the Default mode. Thus, if you use the vanilla [self processDataOnMainThread:dictionaryOfParams] call, the function will start executing regardless of whether the user is scrolling or not. But, with the API call above, iOS will wait for the user to stop scrolling before executing your function.

Avoid Memory Spikes

Every iOS developer dreads the ominous “Low Memory Warning”. In addition to being delivered if the app uses a lot of memory, Low Memory Warnings can also arise if the application’s memory suddenly spikes, even though the overall memory usage is quite small. If your application’s memory doesn’t go down after repeated memory warnings, iOS will kill your app! Tip #3: Always strive to keep your memory profile smooth. Some typical hot spots for memory spikes are:

  • App Launch: Load as few objects as you need. This will speed up launch and prevent memory warnings!
  • View Controller Initialization: New view controller objects are instantiated when they are pushed on the navigation stack or presented modally. Try to use as few views as possible. Or instantiate some views lazily, if you can.
  • UIWebview: UIWebview is notorious for using up a lot of memory very quickly, especially when loading HTML content with heavy images/videos. Its hard to completely control the memory profile with a UIWebview in your application, but loading data lazily is always a good rule of thumb.

Remember, If you keep your application’s memory profile steady and consistent, it will lead a long and healthy life! Check out Advanced Memory Analysis with Instruments for more info.

Avoid unnecessary caching of images

Throughout an iOS application, we need to refer to images in the bundle. More often than not, imageNamed: is an extremely simple and efficient way to do so. But, you should be aware that imageNamed: also caches any image it imports from the bundle. Thus, it is highly efficient for images that need to be reused throughout your application (like icons, background images for buttons etc.). But it can be an unnecessary memory hog for images that are used sparingly. Tip #4: For loading such images, we should instead read them directly from disk and release the memory when we are done using the image.

NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];

[image release];

As a rule of thumb, use imageNamed: with images that are used in UI elements and initWithContentsOfFile: for everything else. Here is a handy category we wrote on UIImage that automatically chooses the right image for retina display screens and reads them from disk.

UImage+ImageNamedFromDisk.h
UImage+ImageNamedFromDisk.m

I hope you find these tips useful in your own development. Please share your own insights into optimizing iOS applications by leaving comments below!

Five Lessons Learned from Migrating to iOS Core Data

A couple of weeks ago we decided to fully switch to Core Data for persistent data storage on our iOS apps. This was a bold decision that would bring us more coherent data representations, performance improvements and make future data migrations a breeze…or so we thought. Whilst there certainly were a lot of gains from switching to Core Data it took us a lot longer than anticipated, and we ran into some unexpected issues and limitations. Since we are surely not the last ones moving existing apps to Core Data, we would like to share some of our lessons learned.

Note: This post assumes that you are already familiar with the concepts of Core Data and its classes. If you are new to the subject, we recommend reading the Core Data programming guide by Apple.

Lesson 1: The more you respect MVC, the easier your migration will be

Core Data knows no mercy with developers who disrespect the MVC principles. Because model objects can get deallocated and reloaded at the will of their managing NSManagedObjectContext you can never rely on them being around unless you specifically retain them (which isn’t a good idea). It is thus unwise to set the model objects as the direct receivers of callbacks (for example, image download). Instead, route all calls through the appropriate controller, which manages the NSManagedObjectContext of the data object, and retrieve the data objects at the time when you need them to store the downloaded information. This does not only solve the availability issue but also makes for much cleaner code as you are forced to minutely adhere to the MVC standards.

Lesson 2: Data migrations are a breeze…if you have XCode >= 4.0.2

If you are already partly using Core Data in your application you will probably need to update your data model, and chances are that you need to provide a migration mapper. Luckily there are some great tutorials on the subject out there; however, you can still lose a lot of time if you forget either of the following:

  1. Turn off automatic data migration (when initializing the persistent data store manager, set NSMigratePersistentStoresAutomaticallyOption to NO)
  2. If you are still running XCode 4.0.0, upgrade to at least XCode 4.0.2. Previous versions have a bug that prevents the migration mappers from being placed in the proper location at link time. Believe us, we learned the hard way.

Lesson 3: Multithreading and Core Data can work very well if you plan ahead

Perhaps the most annoying part about Core Data is that, in its current implementation, the two objects you interact with the most (NSManagedObjectContext and NSManagedObject) are both not yet threadsafe. However, there are best practices, using workarounds, for both updating and loading objects on secondary threads. If you familiarize yourself with them before implementing your designs you will find it pretty easy to create concurrent applications using Core Data. Apple has a great guide on Core Data and concurrency which is well worth the read and nicely explains what to do in both situations.

Lesson 4: NSFetchedResultsController is not always useful

In order to make it easier to use UITableView together with Core Data, Apple has invented the NSFetchedResultsController, which manages the NSManagedObjects associated with a particular fetch request and monitors an associated NSManageObjectContext for changes to those objects. While this setup is great if there is only one UITableView being displayed and there are few changes being made to the underlying data objects, we’ve found that using NSFetchedResultsController, under certain conditions, can adversely effect the (felt) performance of your app. When you have data sets with many changes or if there are frequent changes being made to underlying data objects, it is advisable to manually “bundle” those changes and entirely reload the UITableView instead of incrementally reloading the cells affected. If you meet one of those two cases, consider implementing your own update routine and temporarily deactivating the update mechanism of the NSFetchedResultsController by setting its delegate to nil.

Lesson 5: Performance, performance, performance!

To get the best performance out of Core Data keep the following in mind as you sketch up your refactoring:

  • Multiple small queries are slower than one larger query since every call to performRequest: hits the disk
  • Complex filter predicates can significantly hamper the performance of your queries, keep them as simple as possible
  • Fetching relationships is expensive; avoid them if they do not provide a significant advantage. Flatten your data model where you need to load data together.
  • Only store small data objects directly in CoreData, store larger objects in files and only save the path to them in CoreData (eg. images)

We hope these insights will help you when planning your own migration to CoreData. Let us know if you find other valuable tricks for dealing with CoreData!

Thanks for reading!
~The Pulse iOS team

Line-by-line Speed Analysis for iOS Apps

[This is a guest post written by Tyler Neylon. Tyler is an iOS programmer and founder of Bynomial. He regularly blogs about iOS coding tips at the Bynomial blog.]

The iOS SDK comes bundled with tools in the Instruments app for finding speed bottlenecks in your code. These tools work by regularly inspecting the call stack of your app, and then providing an aggregate view of these call stacks. This is a good method for doing top-down speed analysis, where you want to find the methods eating up the most CPU time on a very large scale.

This post introduces some easy-to-use tools for speed analysis from a bottom-up perspective. Let’s say you’re writing or improving a particular time-critical function. For example, you may be writing a function that reacts to a user’s tap and needs to quickly compute and display results from that tap. In this case, the time profiler in Instruments is not ideal, since it’s hard to isolate a useful amount of data around the exact call stacks and time interval you care about. Instead, you can use CodeTimestamps, the open source tool we’re introducing here.

How to use CodeTimestamps

CodeTimestamps contains two macro sets. There is the simple LogTimestamp macro, which is useful for quick speed checks, and then the LogTimestamp{Start,Mid,End}Chunk macros, which are better for in-depth analysis, such as comparing many runs of multiple functions or loop bodies.

Simple case

Let’s say you want a detailed picture of how much time certain lines in your code take. You can add calls to LogTimestamp like this:

- (void)myMethod {
  LogTimestamp;
  // do some CPU-heavy stuff
  LogTimestamp;  // Line 44.
}

When this code executes, every line with LogTimestamp after the first one will produce a debug log line like this:

* -[MyClass myMethod:]:  44 -    864719 nsec since last timestamp

This tells you the class and method name, the line number, and the number of nanoseconds passed since the previous timestamp. In other words, you get to know exactly how long myMethod takes, down to the nanosecond. It can be useful to throw in a bunch of LogTimestamp lines to get a line-by-line breakdown of what parts of your method are the slowest, so you know where to focus your speed-up efforts.

Advanced case

The above technique is great for looking at a small number of specific code segments, and when no data aggregation is needed. But let’s say you want to find out which iteration of a function out of 10,000 calls was the slowest, or perhaps you will be using many timestamps within a function, and would like to quickly locate the slowest piece. Time to bust out the big guns.

You use the LogTimestamp{Start,Mid,End}Chunk macros similarly to LogTimestamp, except that, within a function, they are expected to be executed in order – Start – Mid, Mid, Mid, (etc.) – End. You can have as many midpoints as you want, including zero. The data you get from these macros is best explained by an simple example. Suppose we want to know which line of the function MyFunction is slowest. Here’s our code:

void SlowCall() {
  sleep(1);  // pause for 1 sec
}

void FastCall() {
  usleep(1e5);  // pause for 0.1 sec
}

void MyFunction() {
  LogTimestampStartChunk;
  FastCall();
  LogTimestampMidChunk;
  SlowCall();
  LogTimestampMidChunk;
  FastCall();
  LogTimestampEndChunk;
}

And here’s the resulting debug log lines when we run the code with one call to MyFunction:

Temp20[36546:207] ==== Start chunk timestamp data (from "LogTimestamp{Start,Mid,End}Chunk") ====
Temp20[36546:207] --- Data for thread 0x4b0dbb0 ---
Temp20[36546:207] + Chunk = MyFunction:28 - MyFunction:34, time = 1.2003s
Temp20[36546:207]     83% in MyFunction:30 - MyFunction:32
Temp20[36546:207]      8% in MyFunction:32 - MyFunction:34
Temp20[36546:207]      8% in MyFunction:28 - MyFunction:30
Temp20[36546:207] ++ Chunk = MyFunction:28 - MyFunction:34, avg time = 1200328449 nsec
Temp20[36546:207] ==== Slowest chunks so far ====
Temp20[36546:207] # Chunk = MyFunction:28 - MyFunction:34, time = 1.2003s
Temp20[36546:207] ==== End timestamp data ====

If we add these chunk macros to another function called MyFunction2, and execute both MyFunction and MyFunction2 about 1,000 times each, then we can quickly find out which one is slower by looking at the “Slowest chunks so far” aggregated data, which is a summary based on all previous calls to all chunks.

The debug output is not immediately supplied — it is deferred by up to 10 seconds. This is because NSLog calls can be notoriously slow. If data were logged immediately, all the timing information would be hijacked by NSLog’s inexplicable processor waylaying. Why, NSLog? But alas, the best we can do is work around NSLog’s lackadaisical lollygagging ways by gathering the timing information in memory, and reporting it periodically outside of the timestamp chunks.

This output is not as beautiful as the graphs in Instruments, but I personally find this approach much easier to use for the purpose of speeding up specific sections of my code.

How to get the code

The files CodeTimestamps.{h,m} are freely available as open source code under the Apache 2 license. They can be downloaded as part of the moriarty library. Click on “Downloads” (near the upper-right corner on the github page), unzip the file you download, and copy the two files (CodeTimestamps.{h,m}) into your Xcode project, making sure Xcode knows about these files (right-click on Classes, then Add > Existing Files… to add them within Xcode). From there, just #import "CodeTimestamps.h" in whichever file you want to add timestamps to, and use them as described above.

I wrote this code while working with Ankit to speed up Pulse on the iPad. I’ve always been impressed by the Pulse team’s steadfast dedication to setting new standards in mobile app user experience, and building custom performance tools like this is just one of many techniques used. I also think it’s awesome that Pulse chose to offer this code as open source. Keep up the great work!

Concurrent Downloads using NSOperationQueues

Most iOS applications have to download data from the internet. Being a mobile developer, sparse resources, limited bandwidth and user-responsiveness needs make this a very interesting problem to tackle. If you are lucky, you might just have one url that you ping to download all the data for your application. But usually, one has multiple urls to download from simultaneously. Since Pulse aggregates your news from multiple sources, this is a particularly important part of the application. After a long search for an elegant and efficient solution for this problem, we discovered that NSOperations and NSOperationQueues make managing simultaneous downloads very easy. This post lists some of our learnings and gives code samples on how to implement it in your own app.

Why NSOperationQueues?

Ideally, you never want to block the main thread that handles user input. Hence, downloading data has to happen in a background thread. We have found that NSURLConnection is an excellent class to download data asynchronously. But, in order to maintain multiple connections simultaneously, one usually has to care about 3 features: (1) Throttling the number of simultaneous downloads (2) Prioritizing connections over one another (3) Easy cancellation and cleanup of such connections. These functions require a lot of bookkeeping that can get quite tedious. NSOperationQueues are extremely helpful in such situations.

An NSOperationQueue is essentially a pool of threads each of which runs a task described by NSOperation objects. It is extremely easy to wrap an asynchronous NSURLConnection in an NSOperation, as we shall see in the next section. Each NSOperation object can be given a priority and added to the queue.

[myOperation setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operationQueue addOperation:myOperation];

An NSOperationQueue object allows you to specify the number of threads is should use and you can easily kill all operations.

[operationQueue setMaxConcurrentOperationCount:3];
[operationQueue cancelAllOperations];

Based on system resources and operation priorities, an NSOperationQueue runs all its operations till they finish. You can pause and resume it at any time, giving you complete control over running tasks in the background with minimal lines of code.

[operationQueue setSuspended:YES];
for (operation in newOperations) {
  [operationQueue addOperation:operation];
}
[operationQueue setSuspended:NO];

Asynchronous downloads using NSOperations

To wrap an NSURLConnection object in an NSOperation, we need to create an NSOperation subclass. When the operation starts, we can initiate an NSURLConnection and implement its delegate methods to collect downloaded data. An NSOperation uses 3 key variables to define its state: isExecuting, isFinished and isConcurrent. Since we want our downloads to run in parallel, we set isConcurrent to YES. Both isExecuting and isFinished need to be tracked in a key-value coding compliant manner. The operation is only considered finished when the isFinished property changes to YES. Check out DownloadURLOperation.h and DownloadURLOperation.m to learn more on how this is done.

New Subclass in Action

Now that we have our DownloadURLOperation subclass in place, we add its objects to operation queues and start downloading data. If an operation queue is empty, an operation starts executing as soon as it is added to the queue.

downloadOperation_ = [[DownloadURLOperation alloc] initWithURL:url];
// Add an observer to get notified when the download finishes
[downloadOperation_ addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:NULL];
[queue addOperation:downloadOperation_];

To process the downloaded data after an operation is finished, we need to observe KVO notifications.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)operation change:(NSDictionary *)change context:(void *)context {
  if ([operation isEqual:downloadOperation_]) {
    [downloadOperation_ removeObserver:self forKeyPath:@"isFinished"];
    [downloadOperation_ release];
    downloadOperation_ = nil;
    NSData *data = [downloadOperation_ data];
    NSError * error = [downloadOperation_ error];
    if (error != nil) {
      // handle error
    } else {
      // process data
    }
  }
}

Whenever necessary, remember to cancel the operation.

- (void)dealloc {
  if (downloadOperation_ != nil) {
    [downloadOperation_ removeObserver:self forKeyPath:@"isFinished"];
    [downloadOperation_ cancel];
    [downloadOperation_ release];
    downloadOperation_ = nil;
  }
  [super dealloc];
}

Benefits

Here is a quick summary of a some important benefits of wrapping NSURLConnections in NSOperations and using NSOperationQueues to manage simultaneous downloads.

  1. Throttling: NSOperationQueue allows you to set the maximum concurrent operations allowed to run. Thus, we can fine tune this number based on device performance and network constraints.
  2. Chained Downloads: If you need multiple downloads to happen one after the other in a sequence, using NSOperations allows you to execute chained downloads easily in a single class.
  3. Code Cleanliness: This pattern allows you to encapsulate downloading and processing data in the same class. For example: YAJLParserOperation.h and YAJLParserOperation.m parseJSON data on the fly as it is downloaded.

Although asynchronous NSURLConnection downloads happen on a background thread, its delegates are always called in the main thread in the NSDefaultRunLoopMode. This means that the delegates would never be called when the user is touching the interface (say while scrolling a tableview or tapping buttons). Even if a connection runs inside an NSOperation, this important property is maintained since we ensure that the connection starts on the main thread.

Here is some sample code that you can play around with to learn more. Please leave comments for any suggestions or improvements.