Performing a block of code on a given thread

This post has been a long time coming, mainly because I cannot claim that I done any of the work to make it possible, for that I have to thank the Big Nerd Ranch and Landon Fuller. Nevertheless, I have found the following category so useful that I thought I would do a post on it anyway.

When I first started using blocks, I thought “I have this block of code, can’t I just run it on a given thread?”. The answer is (thankfully) yes, but Apple don’t supply a simple API to do this in Mac OS or iOS. The category below solves this issue:

@implementation NSThread (MCSMNSThreadCategory)

+ (void)MCSM_performBlockOnMainThread:(void (^)())block{
    [[NSThread mainThread] MCSM_performBlock:block];
}

+ (void)MCSM_performBlockInBackground:(void (^)())block{
    [NSThread performSelectorInBackground:@selector(MCSM_runBlock:)
                              withObject:[[block copy] autorelease]];
}

+ (void)MCSM_runBlock:(void (^)())block{
    block();
}

- (void)MCSM_performBlock:(void (^)())block {
    if ([[NSThread currentThread] isEqual:self]) {
        block();
    } else {
        [self MCSM_performBlock:block waitUntilDone:NO];
    }
}
- (void)MCSM_performBlock:(void (^)())block waitUntilDone:(BOOL)wait {

    [NSThread performSelector:@selector(MCSM_runBlock:)
                     onThread:self
                   withObject:[[block copy] autorelease]
                waitUntilDone:wait];
}

- (void)MCSM_performBlock:(void (^)())block afterDelay:(NSTimeInterval)delay {

    [self performSelector:@selector(MCSM_performBlock:) 
               withObject:[[block copy] autorelease] 
            afterDelay:delay];
}

@end

This category adds a set of (simple) methods to the NSThread class, that allows you to run a block on any thread that you have a reference to. (You may notice that the Big Nerd Ranch prefix their category methods with BNR and I use MCSM, this is due to Objective-C not supporting namespaces).

For more information on blocks, see Apple’s Blocks Programming Topics documentation.

Alongside blocks Apple introduced Grand Central Dispatch (GCD) in Mac OS 10.6, and I suggest that anyone implementing processor intensive tasks that require code to be executed off of the main thread should try and implement their code using GCD. That being said there are situations (predominantly due to the use of legacy APIs/libraries) whereby the use of a dedicated thread is still required.

Say you have a network thread (networkThread), and on this thread is network socket which is maintaining a persistent connection to a server, and a parser for the data that is received on that socket. (There is a common mistake I see, whereby programmers do the network I/O off of the main thread but still parse the data on the main thread, thus locking up the application’s UI when the data is being parsed).

Normally the data you want to send over the network originates from something that has been triggered from the application’s UI, and therefore the main thread. You then need to get this data onto the network thread so it can be sent.

Say you wanted to send a person’s first name (firstName), last name (lastName) and company name (companyName) over the network.

Ideally you would have a method that looked something like:

- (void)sendFirstName:(NSString *)firstName 
lastName:(NSString *)lastName 
companyName:(NSString *)companyName;

This method would convert the data into a format that can be sent over the network (e.g. XML, JSON etc), and queue it on the socket for sending.

The issue is if two threads try to use the same socket at the same time your application will crash, so this method can only ever be called from the networkThread. This is also relevant for other non-thread safe APIs.

So for this example we will rename it to:

- (void)onNetworkThreadSendFirstName:(NSString *)firstName 
 lastName:(NSString *)lastName 
companyName:(NSString *)companyName;

and keep the original method for use on the main thread.

So how do you call a method on another thread? Well you can’t directly, you have to use:

- (void)performSelector:(SEL)selector 
 onThread:(NSThread *)thread 
 withObject:(id)object 
 waitUntilDone:(BOOL)wait;

The only problem with this is that the selector you call can only take one argument (using the withObject parameter), and in this example we want to pass 3.

So what we need to do is put all of the arguments into one object. You could do this using an array (you would need a fixed amount of arguments to do this), a custom object (overkill if it is only going to be used as an argument to a method) or a dictionary which is what I will be using.

- (void)onNetworkThreadSendArguments:(NSDictionary *)arguments;

This means that you would end up with an implementation such as the following:

- (void)sendFirstName:(NSString *)firstName 
 lastName:(NSString *)lastName 
 companyName:(NSString *)companyName{

NSDictionary *arguments = [NSDictionary dictionaryWithObjectsAndKeys:
firstName,@"firstName",
lastName,@"lastName",
companyName,@"companyName",nil];

[self 
performSelector:@selector(onNetworkThreadSendArguments:) 
onThread:networkThread 
withObject:arguments 
waitUntilDone:NO];

}

- (void)onNetworkThreadSendArguments:(NSDictionary *)arguments{

NSString *firstName = [arguments objectForKey:@"firstName"];
NSString *lastName = [arguments objectForKey:@"lastName"];
NSString *companyName = [arguments objectForKey:@"companyName"];

[self 
onNetworkThreadSendFirstName:firstName
lastName:lastName
companyName:companyName];
}

- (void)onNetworkThreadSendFirstName:(NSString *)firstName 
lastName:(NSString *)lastName 
companyName:(NSString *)companyName{
    // format and send data
}

So before 10.6 this technique would be common practise, but it means you have to write extra methods to pack and unpack arguments for cross thread calls.

Thankfully we can get this down to 1 method call using our block category on NSThread:

- (void)sendFirstName:(NSString *)firstName 
 lastName:(NSString *)lastName 
 companyName:(NSString *)companyName{

    [networkThread MCSM_performBlock:^{
        // format and send data
    }];
}

As the block of code is always executed on the network thread, this method can be called from any thread. As you can see blocks can be very useful even when used without Grand Central Dispatch.

This category is available on github here.