[Objective-C + Cocoa] The Poor-man’s Multithreading

I’ve been doing a fair bit of coding in Objective-C lately, more specifically using Apple’s Cocoa API. There’s a lot to like about Objective-C as a language. Predating Java by nearly a decade, here is a reflective language that more closely adheres to that fundamental object-oriented paradigm of message-passing between objects than many more-modern languages do. This is even more impressive considering that the language is built on top of C, a 38 year old language with no support for objects, reflection, most other concepts introduced by the object-oriented paradigm, or even a convenient syntax for declaring and using data structures.

Conceptually, one can reason out that Objective-C accomplishes many of its neat tricks by using the performv: selector and a few clever lookup tables placed in strategic locations. For instance, if the member functions for an object are referenced in a function table that is keyed by function name (or more appropriately, selector), and the performv: selector always consults this table when performing a method invocation, then adding new functions to an object can be accomplished, even at runtime, by simply adding some new table entries. It’s a pretty elegant design, really, although that’s a subject for another day, perhaps.

The subject for today is a somewhat simple, somewhat less elegant method that is an integral part of the Cocoa API:

- (void)performSelector:(SEL)sel withObject:(id)arg 
		afterDelay:(NSTimeInterval)secs

What this method does is schedule a given method invocation on the current run loop, to be executed after some amount of delay, specified in seconds. As a brief aside, “run-loops” are an outdated, non-intuitive, and over-complicated concept, and they should go away. Viewed abstractly, a run loop is simply an execution context. It executes a stream of commands serially relative to other commands in the run loop, and in parallel relative to commands being executed in other run loops. In essence, a run loop is simply a thread. It provides an execution context that is asynchronous from other execution contexts in the runtime environment, just like a thread does. And there is little reason to complicate the matter by inventing a new concept to use to model something that every modern Computer Science student is exposed to in their first year of college.

But anyways, the performSelector: method (and its numerous variants) allows a developer to schedule a particular method invocation to occur at some later point in time on the current thread (I shall not be using the term “run loop” any longer). Other instructions on the thread continue to execute as normal between the time performSelector: is called and the time when the target method is invoked (i.e. using this method does not block the current thread). It’s almost, but not quite, like scheduling the execution of a function closure, and can be used to create a kind of poor-man’s multi-threading.

As noted, the method invocation is actually scheduled to execute on the current thread, so there is no true concurrency occurring here; merely the illusion of concurrency. But in many cases, the illusion of concurrency is all that’s really required. To draw a parallel to other languages, the performSelector: method is comparable to the setTimeout() function that is present in both JavaScript and ActionScript. Both of these languages use a single-threaded runtime environment, but can provide the illusion of concurrency through use of the setTimeout() call. Conceptually, the execution engine that these languages use is simply processing a queue of function closures. While there is a closure that is scheduled to execute, the engine dequeues and executes it, otherwise it sleeps until there is a new closure scheduled in the queue. And so it is in a Cocoa thread, except that instead of a queue of function closures, you have a queue of selectors (or “messages”, or “method invocations”, as you prefer).

There are a number of uses for this not-quite-concurrency, such as monitoring the status of an object or operation and then posting a notification to some delegate when that status changes. For instance, here is some simple code to detect the end of a UIView startAnimating sequence:

@implementation AnimatingView 
- (void) checkStatus {
	if([self isAnimating]) {
		//animation is still running, check later
		[self performSelector:@selector(checkStatus)
			withObject:nil afterDelay: 0.1];
		return;  //be sure to do this here
	}

	//animation is done, notify and release our delegate
	[animDelegate notifyAnimationComplete];
	[animDelegate release];
}

- (void) runAnimationAndNotify: (id) objToNotify {
	//[set up your animation stuff here]
	
	//retain the object that we want to notify
	animDelegate = [objToNotify retain];
	[self startAnimating];  //start the animation
		
	//schedule a status notification check
	[self performSelector:@selector(checkStatus)
		withObject:nil afterDelay: 0.1];
}
@end

In many other languages, the above code can only be implemented by spawning a new thread to monitor the status of the animation (or by blocking/sleeping the current thread while the animation is running). You can do it that way in Objective-C as well, but the above method has a couple of advantages over using a dedicated thread:

  • No thread creation overhead. Since the current thread is used, no additional resources need to be allocated on spawning a thread just to handle the notification dispatch.
  • No need to worry about concurrency/synchronization issues. Because the notification is sent from the same thread that started the animation, there is no need to worry about the notification arriving when the calling object is in the middle of doing something else (unless your program has other threads that are making use of that same object).

Of course, there are some caveats associated with this approach, as well:

  • If the current thread blocks or sleeps, the notification will be delayed. Obviously if you are doing things on your thread that would cause it to block or sleep for a long duration of time, you should not use this approach.
  • If too much other work is being done on the current thread, then the timing of the notification will be unreliable. That’s just the nature of the beast, just like how ActionScript and JavaScript timers lose their accuracy as CPU load approaches 100%. If your workload is heavy enough to saturate a CPU core, you are better off splitting it up into multiple full-fledged threads.

Overall, however, this is a useful technique for those situations where you want concurrent behavior without having to pay the resource and synchronization cost of true concurrency. It’s not appropriate in every situation, but if you have a simple task that you’re thinking about offloading onto a dedicated thread, this approach may be a quicker and simpler alternative.

Finally, a coding post!

This entry was posted in coding, concurrency, objective-c and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>