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.
Thank you very much. This is one of the most useful tips I’ve seen in a long time!
Since I’m using this with iOS without ARC, I made the following change to automatically handle the autorelease pool in background threads:
+ (void)MCSM_performBlockInBackground:(void (^)())block{
[NSThread performSelectorInBackground:@selector(MCSM_runBlockInBackground:)
withObject:[[block copy] autorelease]];
}
+ (void)MCSM_runBlockInBackground:(void (^)())block{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
block();
[pool release];
}
Thanks very much for the nice code.
One question:
In your code,
- (void)MCSM_performBlock:(void (^)())block{
if ([[NSThread currentThread] isEqual:self])
block();
else
[self MCSM_performBlock:block waitUntilDone:NO];
}
If the currentThread is the specified thread, the block will execute and the method will not return until block completed.
If the currentThread is not the specified thread, the block will be queued and the method will return immediately.
Seems to me this is an inconsistent behavior, and the caller might need to handle the two scenarios differently.
Why do you make it this way, instead of using waitUntilDone:YES in the else block?
Hi ElfeXu,
It is just a convenience method and as the method doesn’t return anything, I thought that there was no point in waiting for it to execute. If you call the method on the current thread it will execute at that time anyway, so there is no disadvantage of doing this.
If you wanted to have complete control then you could use MCSM_performBlock:waitUntilDone:
Although the method does not return anything, if the caller defines __block variables, the behavior will still be different. I just feel the inconsistent behavior might cause potential problems.
I understand what you are saying, maybe the method just needs a more descriptive name
This snippet does SEEM very useful, but I’m having a rather big problem…a leak, to be precise. And I can’t seem to get rid of it.
Maybe you can help me.
I’m having a network dispatcher running in a thread.
The object in question is allocated and initialised. (retainCount 1)
After that, it’s added to a class variable in a UIView with retain (retainCount 2)
Subsequently, it gets added to a collection (retainCount 3)
At the end of the initialization, the alloc gets released (retainCount 2)
Upon releasing the view associated with it, I release the retained variable, decreasing the retain count to 1.
The ‘Closing code’ also severs the network connection, breaking the select.
The network dispatcher then detects the closing and removes the object from the collection, making the retainCount drop to 0 (and the destructor getting called)…in theory, that is.
Now, for the actual leak code.
I’m doing (function call, passing a selector):
[NSThread MCSM_performBlockOnMainThread:^{
if([theDelegate respondsToSelector:sel])
[theDelegate performSelector:sel withObject:nil];
}];
I once had it withObject ‘self’, but after the first problems I changed it to nil…to no avail.
Everything works fine, if this snipped is called from the MainThread (obviously, since it doesn’t call ‘performSelector onThread:’ then, but executes it as normal code)…but as soon as my Networkthread calls it, it retains the object the block is from…but doesn’t release it any more.
It is called twice in the course of the objects lifecycle (once to say: ‘I finished loading’ and once to say: ‘I have disconnected’).
At the supposed end of the lifecycle, it the retainCount sits at 2.
I already followed Alan Birds advice and included the AutoReleasePool into the ‘runBlock’-method, but it doesn’t change a thing.
I am aware, that I might do well, to make a ‘__block’ variable of ‘self’…but in this code, I’m not even passing it.
Can you tell me what I’m doing wrong?
Hi,
Im not sure TBH, as you alluded to anything that is retained by the block should be released after the block has executed, so it should’t create any issues with retain counts.
Is it anything related to the theDelegate or how you create the selector (assuming you are not just doing @selector(selectorName:)?