This question is similar to this question with automatic reference counting thrown in.
I have an NSOperation
subclass that accepts a block argument that is intended as a callback to the main (UI) thread. My original intention was to perform some operation in the background, and then use dispatch_async
and the main queue to perform the callback.
Original premise:
@interface MySubclass : NSOperation {
@protected
dispatch_block_t _callback;
}
- (id)initWithCallback:(dispatch_block_t)callback;
@end
@implementation MySubclass
- (void)main
{
// Do stuff
if (![self isCancelled]) {
dispatch_async(dispatch_get_main_queue(), _callback);
}
}
@end
Problems arise when all references to a UIKit object outside the scope of the block are removed. (E.g. a UIViewController
is popped off a navigation stack.) This leaves the only reference to the object inside the block, so the object is deallocated when the block is, on the thread where the block is deallocated. Deallocating a UIKit object off the main thread crashes the app with the error message 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...
As a workaround, I added a __block
modifier to the callback ivar, and am using dispatch_sync
to make sure everything released is on the main thread.
@interface MySubclass : NSOperation {
@protected
__block dispatch_block_t _callback;
}
- (id)initWithCallback:(dispatch_block_t)callback;
@end
@implementation MySubclass
- (void)main
{
// Do Stuff
if (![self isCancelled]) {
dispatch_block_t block = ^{
_callback();
_callback = nil;
};
// Cover all our bases to prevent deadlock
if ([NSThread isMainThread]) block();
else dispatch_sync(dispatch_get_main_queue(), block);
}
}
@end
I am wondering if there is a better way to accomplish something with this premise. My workaround feels hacky, and I don't like that I might end up with several operations in my queue all waiting for a turn on the main thread before they can complete.