Search
Recommended for You

Timely UI Updates


Laggy UI updates are a frequently occurring problem on the iPhone. With operationally intense work, your GUI may not always keep sync with your requests, particularly when you try to update status information to let the user know how things are going. If you've encountered this problem, threading is generally the best way to handle this.

Threading

When loops or other intense operation keep your GUI from updating, splitting your program into threads provides a way to bring your interface back to life. Create a new thread by using the NSThread detatchNewThreadSelector: toTarget: withObject: method.

[NSThread detachNewThreadSelector:@selector(listenForRequests) toTarget:self withObject:NULL];

This call launches the listenForRequests method on a new thread, allowing you to separate that functionality from your main GUI operations.

Keep your GUI operations in the main thread. This includes any operation that responds to or changes any on-screen elements including UILabels, UIButtons, and so forth. My iPhone approach here mirrors what I've always done on the Mac: move that intense operation to the secondary thread and keep the primary thread devoted to the interface.

Threaded Methods

When building a threaded method, assign that thread its own autorelease pool. Your method retains access to all the standard class variables (self, etc) but any objects you allocate in that thread will leak unless you set up that pool to handle them.

- (void) listenForRequests
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        ...behavior...
       [pool release];
}

Communicating with the GUI

With this dual-thread approach, your secondary thread will want to update values in the main GUI. To do this, use intra-application notifications. The NSNotificationCenter provides a way to send messages between objects. Use the default center to post a notification that can be listened to and responded to from your main thread.

[[NSNotificationCenter defaultCenter] postNotificationName:@"PostText" object:request];

This sample sends the "PostText" notification along with a string object that is the text to be posted. This notification can be received by any object that has subscribed to it as an observer:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextPost:) name:@"PostText" object:nil];

The name parameter specifies the notification to subscribe to. If you leave this option out (by passing nil instead), you can subscribe to all notifications generated within the application.

In this sample, handleTextPost: is responsible for updating the text in a UILabel or UITextView. It does this by passing the request along to an updateText: method and specifying that this must occur on the main thread.

- (void) handleTextPost: (NSNotification *) notification
{
	id nobj = [notification object];
	[self performSelectorOnMainThread:@selector(updateText:) withObject:nobj waitUntilDone:YES];
}

The notification handler (this is a simple case, but you can easily see how this could be generalized for more flexible use) acts as a middle-man. It conveys requests from the secondary thread and transforms them into updates on the main thread. This ensures that the updates happen in a timely manner without those updates getting blocked.

The performSelector workaround

Many iPhone applications don't require a fully threaded solution to GUI blocking. There's actually a simple little workaround that allows you to do your updates and avoid blocking issues without adding threads. That solution is performSelector: withObject: afterDelay:. Instead of doing:

[myLabel setText:@"Updated Text"]

which may or may not update in time, you'll get far better results by calling:

[myLabel performSelector:@selector(setText:) withObject:@"Updated Text" afterDelay:0.1f];

This workaround is ugly but you'll find that it can fix many update issues. A well-threaded program is often a better design decision but this trick may help you past a stumbling block or two without having to re-work your entire system.

Got more thoughts? Have some feedback or corrections? Leave them in the comments and I'll update the post as needed.

AddThis Social Bookmark Button
Comments (8)

8 Comments

mikkel said:

interesting post...

I make a couple of apps that gathers info from the web at regualr intervals...this approach will surely help me...

Michael said:

[myLabel performSelector:@selector(setText:) withObject:@"Updated Text" afterDelay:0.1f];

Often it's better to use afterDelay:0 rather than afterDelay:0.1.

A value of zero means that the method will on performed the next cycle through the run loop, not a tenth of a second later.

michele said:

Can you please expand on the performSelector workaround? What does it get me?

benwulf said:

I wonder why use the notification center as opposed to directly use the performSelectorOnMainThread in order to send to the mainthread the required message?
In my mind both would work as well but skipping the notification would make the program clearer

Erica Sadun said:

I just like having all my GUI updates centralized into a single handler. All notifications go to that handler, which is then responsible for passing them off to the GUI.

Henning said:

But the documentation to NSNotificationCenter says that the notification will be handled within the same task. That's what I'm seeing. The same thread is picking up the notification as sending it. That's not what I want.

najeeb said:

this method does leave the message in the same thread where it was posted from. not safe!

Ron said:

Now, how would I fadeout?

Leave a comment