How to make your apps feel responsive and fast? (Part 2)

by Christian Apers

In my previous blog post I talked about responsiveness and performance from a user perspective. In this part I will unveil the world of optimization techniques. So this is where the tech stuff starts, if you don’t know what an NSString is then stop reading here…

Performance

As you have read in my previous post responsiveness is much more important than performance, but we are going to start with performance optmization.

Theoretical approach (don’t do this)

One of the things I learned from my Computer Science classes is to approach performance problems theoretically. Take a look at these two code examples

  //a very large array with N elements
  NSArray * array = [self createArray];
  for(id object in array) {
    [object performSomeAction];
  }
  for(id object in array) {
    [object performAnotherAction];
  }

vs.

  //a very large array with N elements
  NSArray * array = [self createArray];
  for(id object in array) {
    [object performSomeAction];
    [object performAnotherAction];
  }

In the first example your program will have to loop twice over your very large array. This takes more time than the second example where all operations are grouped per iteration. In the second example your program only has to walk through all the array elements once.

Looking at your code in this way probably results in more efficient algorithms. But I’d advice you NOT to go there.
Nowadays compilers are very good at making your code more efficient – and changing the code as in the example above has very little impact on the overall performance.
Mixing up your code to make it more efficient usally results in unreadable and therefore unmaintainable code.

Measure!

Apple has provided us with some very powerful tools. Why should we exhaust our brains with reasoning about what codes takes too much time? We can also measure it!
Number one rule when optimizing software is to start with the improvements that have the biggest impact. Don’t waste your time on optimizing code that has little impact, but first try to analyze what parts of your code need optimization.

In Xcode’s menu select ‘Product’ and then ‘Profile’ .
After a successful build Xcode will launch Instruments and you are presented with this popup:

There are many Instruments that help you analyze your app. When talking about performance the two most important ones are the Time Profiler and the Core Animation tool (when you’re not using Open GL).

Time Profiler

When we talk about performance one cannot live without the ‘Time Profiler’. If your program performs too slow the first thing you have to do is investigate what takes so long. There are always parts of your code that can be more efficient. But before you start rewriting please do some research to get a list of the most time consuming parts of your program.

When you run a Time Profile you get to see a list of function names. This screens shows a list of functions/methods ordered by the amount of time that your program spends in those methods. You can play with the timeline and add a start- and endpoint to focus on a specific part of your program.

Turn on ‘Invert call tree’ and turn on ‘Hide system libraries’ to get a list of the most time consuming methods that you have written yourself. With ‘Invert call tree’ turned off you have to drill down all the way to the deepest level to get to the time consuming methods. Feel free to play with these settings to get the results that you find most useful.

Double click on a method to find out what line of code takes too much time:


This is one of the most powerful features of the Time Profiler. You can find out what lines of code need optimization.

In one of our projects we used a lot of NSDateFormatter’s and NSNumberFormatters to display dates and times in various formats and timezones.
When I ran the Time Profiler it turned out most of the time was consumed by this line of code:

NSDateFormatter * df = [[NSDateFormatter alloc] init];

I never could have figured this out without the Time Profiler. Creating a new NSDateFormatter every time was not necessary at all, so we have rewritten our code:

NSDateFormatter * df = [self sharedDateFormatter];

with the following code to prevent that a new dateformatter was created every time:

static NSDateFormatter * sharedDateFormatterInstance;
- (NSDateFormatter)sharedDateFormatter {
   if(sharedDateFormatterInstance == nil)
      sharedDateFormatterInstance = [[NSDateFormatter alloc] init];
   return sharedDateFormatterInstance;
}

With this simple change the init function that turned out to be so time consuming was only called once in the whole program. We saved seconds with this small change.

Well that’s it for the Time Profiler. Make sure that you run Time Profiles as part of your daily workflow. It is much easier to optimize as you write code than doing the optimization afterwards.
Time to move on to the next tool.

Stuttering / flickering

Unfortunately the Time Profiler does not highlight all performance issues. When your app’s frame rate (measured in Frames Per Second) drops below 60 FPS (frames per second) the app feels less smooth. A low frame rate results in stuttering scrollviews and jerky animations.

A drop in frame rate basically means that the iPad cannot catch up the rendering speed. The ideal frame rate for your eyes is 60 FPS, which means that every frame should be rendered in 1/60 seconds.
This is where the Core Animation instrument comes in. Use this tool to measure the FPS of your app. On the left you have some checkboxes that help you track down what causes a low frame rate. I’ll go over the most important ones.

Offscreen rendering

The first is off-screen rendering. Off-screen rendering means that a certain part of your app is rendered twice for every frame.
Most off-screen rendering is caused by shadows and masks. iOS first renders the layer to compute the exact shape of the shadow, and then it renders the layer with the shadow. A mask also requires that the original layer is rendered before the mask is applied.

When your app is forced to do off-screen rendering this has a major impact on the frame rate: rendering this part takes twice as long. Turning this checkbox on will highlight all parts that are rendered off-screen.

When the off-screen rendering is caused by shadows you can often solve it easily. The heavy calculations when rendering a shadow take place in the part where the exact shape of the shadow is computed. The layer that will get the shadow will have to traverse it’s whole sublayer hierarchy to compute the exact shape. But when you already know the shape of your layer you can set the shadow path yourself. The shadow path determines the shape of the shadow.

    //we now assume that thumbView is rectangular. With the bezier path we create nice round borders.
    yourView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:yourView.bounds] CGPath];

Now run your app again with the Core Animation tool and notice that no off-screen rendering takes place. Your app should run much smoother now.

Blended layers

Every time your iPad renders a frame (+/- 60 times per second) it has to compute the color that is displayed in a pixel. When there is an opaque layer on top it is very easy to compute the final color for a pixel, it just draws the color of the top layer. A non-opaque layer fills it entire frame and has no opacity.
The more non-opaque layers in your view-hierarchy the heavier the rendering computations become. If the top layer is non-opaque the rendering engine has to check the colors of the layer below that layer, and if that layer is also non-opaque it has to check the color of the layer below that layer too, etc, etc. You can check the number of blended (non-opaque) layers with the ‘Blended layers’ checkbox.

Dark red indicates that this part is quite hard to render. There are probably multiple non-opaque layers on top of eachother. If your apps shows too much red, consider to flatten your view hierarchy. Add backcolors and set opaque to YES for layers that are fully opaque. In this way you tell the rendering mechanism that the layers below this layer don’t have to rendered.

Rasterization

Some layers are just difficult to render (due to shadows, masks, complex shapes, gradients, etc). To deal with those layers iOS has an API to ‘cache’ the layer. It creates a bitmap of a layer under the hood. In this way the layer gets rendered less often.

  [layer setShouldRasterize:YES];

Advantage of enabling this is that the rendering time of that layer has less influence on your overall frame rate. Disadvantage is that the ‘bitmap’ takes memory, the creation of the initial bitmap takes a bit more time and when you scale the layer it might get pixelated (because it is a bitmap).
When you use rasterized layers set the checkbox ‘Color Hits Green and Misses Red’ to see if rasterization is a good idea. If the rasterized layers turn red too often rasterization is probably not useful. The bitmap is thrown out of memory and has to be regenerated too often – red denotes that the regeneration of the bitmap occurs too late. Consider to rasterize a smaller part of your layer hierarchy of try to decrease the rendering time.

Responsiveness

The above was all about performance. Improving your application’s performance usually improves responsiveness, but this is not always the case. I’ll zoom in on some techniques you can use to keep your interface responsive.

Threads

A thread can be seen as a sequence of actions a CPU can execute. When one method A calls another method B, and that method calls method C, all code is executed sequentially. And this is a good thing to have, it would be really annoying when you write code and you don’t know in what order your lines of code are executed by the CPU (this would lead to strange behaviour).

One application can have multiple threads that run in parallel. The CPU is constantly distributing its time across different threads. Each thread can perform some stuff for a very short amount of time. This is nice behaviour because if a CPU would be able to execute only one thread at a time then the whole device would block when a thread launches some time consuming task. In this situation the OS would not be able to interrupt. Imagine the situation where your home button would take more than 5 seconds because an app is still doing some calculations.

Main thread

There is one thread that is very important to understand. This is your application’s main thread. All user input and UIKit rendering is performed on the main thread. If you haven’t thought about threads in your application you might assume that everything your application does is performed on the main thread.  (This is not entirely true because iOS has a lot of optimizations that use multiple threads under the hood).

Well in some applications it is sufficient to use only the main thread. All code is executed linearly (exactly in the order you’d expect it to run) and if your application performs well you are ok with this setup.

But as theads can only do one thing at a time you might run into trouble when the main thread executes a tasks that takes longer than a few hundred milliseconds. One of the tasks of the main thread is to respond to user input (tap, touches, gestures, etc). So when your app performs some heavy tasks it should receive and respond to touches at the same time.

That’s why it is a very bad idea to execute code like this on your main thread:

NSURLConnection * conn = [NSURLConnection sendSynchronousRequest:req returningResponse:res error:&error];

Or slightly more obfuscated

NSData * data = [[NSData alloc] initWithContentsOfURL: someExternalURL];

Doing so is very dangerous because you have no idea for how long your user interface will be inresponsive.

Wrapping it together:

  • Most code runs in the main thread, this includes all UIKit code and all event handling.
  • The main thread (as any thread) can do only one thing at a time.
  • If your application performs some task in the main thread that takes 3 seconds, your application is not responsive for 3 seconds.
  • You can create or use other threads than the main thread to perform time consuming tasks to avoid a blocking user interface.

Grand Central Dispatch

So how to use threads? In general it is preferred to use Grand Central Dispatch (GCD). GCD and threads are not exactly the same. I will not go in to this now, but you might see GCD as a powerful mechanism built on top of threads. GCD is designed to run code concurrently, and is especially powerful on multi-core systems.

To use GCD we first create a dispatch queue. You can create your own queue like this:

    //blocks added to this queue are executed serially
    dispatch_queue_t backgroundQueue = dispatch_queue_create("yourqueue", 0);

or you can use one of the system’s background queues. These system queues are configured such that all blocks of code can run concurrently. Use dispatch_queue_create to create a serial queue, this is useful when your blocks of code should run serially.

    //blocks added to this queu can be executed concurrently
    dispatch_queue_t existingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

You can dispatch code to your queue from another thread. dispatch_async will run asynchronously, which means that the functions returns immediately (it does not wait for the block to finish).
From there you can dispatch again to another queue (e.g. the main queue to notify that your heavy operation is finished).

    dispatch_async(backgroundQueue, ^{
        //your code that should run in the background
 
        //do some heavy work here.....
 
 
        dispatch_sync(dispatch_get_main_queue(), ^{
            //notify the main thread here
        });
    });

Allow the user to interrupt at any time

Ok so threads can be very powerful. But threads do not solve all your problems:

  • UIKit is not thread safe. This basically means that all classes and methods have the prefix UI should not be called from a background thread
  • It is hard to stop a thread once it started executing its code
  • Threads make your code complex. You cannot access the same variables or memory from different threads unless you know what you are doing.

This brings us to the last topic: runloops. Runloops provide a mechanism to run code on the main thread while your app is still responsive. The main runloop is a continuous loop that runs on your main thread. During each loop it listens to user input, it updates the screen and performs scheduled operations like timers (it does even more but that’s not relevant here). The image below is from the Apple Documentation and explains what the runloop does in every loop. When your app becomes unresponsive this is because the runloop is blocked by some time consuming block of code. If you are able to break down your time consuming block of code into smaller blocks of code and schedule them on different runloops your app listens to user input everytime before it executed the smaller block of code.

To do this you must break your code into smaller pieces and schedule them each on separate runloops. There are different ways to do this:

 
- (void)someHeavyTaskPartA {
   //first part of heavy stuff
 
   //now call the next piece of code on the next runloop with performSelector: afterDelay:
    [self performSelector:@selector(someHeavyTaskPartB) withObject:nil afterDelay:0.0];
}
 
- (void)someHeavyTaskPartB {
   //second part of heavy stuff
 
   //now call the next piece of code on the next runloop with performSelector: afterDelay:
   [self performSelector:@selector(someHeavyTaskPartC) withObject:nil afterDelay:0.0];
}
 
- (void)someHeavyTaskPartC {
.......

Now this is not a very convenient way of coding so you can also use NSOperations.

    //because this code must run on the main thread we use the mainQueue. 
    //if your code can safely be executed on a background thread you can 
    //also create your own operation queue that runs in the background like this:
    //NSOperationQueue * yourQueue = [[NSOperationQueue alloc] init];
 
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        //first part of heavy stuff
    }];
 
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        //second part of heavy stuff
    }];
 
    //etc...

Cancellation

When you allow your user to interact with the app before all your code has finished running you have to think about cancellation.
You could use a simple boolean or integer to cancel blocks of code that are already scheduled.

static int requestNumber = 0;
 
- (void)cancel {
    requestNumber++;
}
 
- (void)scheduleSomeCode {
 
    int currentRequestNumber = requestNumber;
 
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
         if(requestNumber == currentRequestNumber) {
              //part of heavy stuff that will not be executed if you have called cancel
         }
    }];
 
 
}

Well that’s it! It’s a bunch of information but I hope you enjoyed reading it. You might not need all techniques in every app but it is important to have a set of tools to start with when your app needs optimization.
Please provide feedback if you have any suggestions, comments or questions.

christian@touchwonders.com
@caapers

Christian Apers