iPhone Development – Keeping the UI responsive and a background thread pattern

One of the biggest mistakes you can make as an iPhone developer is running any long tasks on the main thread.

Let’s take a concrete example, say you want to retrieve some data from a web server somewhere. If you run this method on the main thread, all user interface activity is blocked until it finishes. The app will cease to respond to any user input (bar a reset / program kill) and the screen will freeze. Not good.

As any programmer who has worked with interfaces should know, all long running tasks need to be run on a background thread. One more vital point is that any changes to the user interface (ANY changes to a UI component) must always be run back on the MAIN thread. That is because those components are not thread safe and should not be changed by multiple threads. So let’s say after you have fetched your data, you want to change a label on the screen, this must be done on the main thread. I’ve come up with a little pattern I use in these cases, for example:

- (IBAction) performLongTaskOnClick: (id)sender
{
    statusMessage.text = @"Running long task";
    [self performSelectorInBackground:@selector(performLongTaskInBackground) withObject:nil];
}

- (void) performLongTaskInBackground
{
    // Set up a pool for the background task.
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    // perform some long task here, say fetching some data over the web.
    //...

    // Always update the components back on the main UI thread.
    [self performSelectorOnMainThread:@selector(completeLongRunningTask) withObject:nil waitUntilDone:YES];

    [pool release];
}

// Called once the background long running task has finished.
- (void) completeLongRunningTask
{
    statusMessage.text = @"Finished long task";
}

To summarise, we have 3 methods, performLongTaskOnClick might be called in response to a button click say, and kicks off the long running task on a new thread with performLongTaskInBackground. Then when that finished it calls completeLongRunningTask back on the main thread. Notice that we set up an autoreleasepool in the background method, you must do that or you will be leaking memory from that thread. Now you should keep your iphone app nice and responsive.

6 Responses to “iPhone Development – Keeping the UI responsive and a background thread pattern”

  1. HZC says:

    Any particular reason you waitUntilDone:YES? Would you use YES in general? I tend to use NO. I’ve not come across a situation yet where I need to wait.

  2. ashok kumar says:

    I just copied this code. But while try to click the button the application crashes with the crash report

    “bool _WebTryThreadLock(bool), 0x5d1fbc0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now…”

  3. wicheda says:

    @ashok That sounds like you are running a UIKit method (something that affects the UI) in the performLongTaskInBackground method still. It also sounds like it has something to do with a UIWebView, maybe you are setting the content of a UIWebView? Make sure you only do that in the completeLongRunningTask method.

    Cheers,

    Dan.

  4. sharktooth says:

    If i intend to do something using ur code, i loose the data on mainthread?? and meanwhile main thread remains stuck??

    - (IBAction) performLongTaskOnClick: (id)sender
    02 {
    03 statusMessage.text = @”Running long task”;
    04 [self performSelectorInBackground:@selector(performLongTaskInBackground) withObject:nil];
    05 }
    06
    07 - (void) performLongTaskInBackground
    08 {
    09 // Set up a pool for the background task.
    10 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    11
    12 // perform some long task here, say fetching some data over the web.
    //calling other class where i read and fill xml data from loacal saved xml file to my structure
    13 xmlClassObject = [[XMLCLASSFILE alloc] init];
    [ xmlClassObject startreadingxmlfile];

    14
    15 // Always update the components back on the main UI thread.
    16 [self performSelectorOnMainThread:@selector(completeLongRunningTask) withObject:nil waitUntilDone:YES];
    17
    18 [pool release];
    19 }
    20
    21 // Called once the background long running task has finished.
    22 - (void) completeLongRunningTask
    23 {
    24 //using above xmlclassobject i am accessing its data and filling data to UI
    xmlclassobject.structdata…
    25 }

    I used the same function as said above but get Mainthread stuck??
    And also looses data saved in struct via xmlclassobject though i am not releasing my xmlclassobject ??

  5. sharktooth says:

    Please advice on this query as i am stuck, as my mainthread is stucked??

  6. wicheda says:

    @sharktooth , the logic in your code seems fine, is the method startreadingxmlfile synchronous or does it start its own thread? Is xmlclassobject back on the main thread in completeLongRunningTask actually nil or just not populated with data?

Leave a Reply