Mike Ash created an example of using blocks to handle callbacks from sheets, which seems very nice. This was in turn updated to work with garbage collection by user Enchilada in another SO question at beginSheet: block alternative?, see below.
@implementation NSApplication (SheetAdditions)
- (void)beginSheet:(NSWindow *)sheet modalForWindow:(NSWindow *)docWindow didEndBlock:(void (^)(NSInteger returnCode))block
{
[self beginSheet:sheet
modalForWindow:docWindow
modalDelegate:self
didEndSelector:@selector(my_blockSheetDidEnd:returnCode:contextInfo:)
contextInfo:Block_copy(block)];
}
- (void)my_blockSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
void (^block)(NSInteger returnCode) = contextInfo;
block(returnCode);
Block_release(block);
}
@end
While enabling GC, this does not work with Automatic Reference Counting (ARC). Myself, being a beginner at both ARC and blocks, can't get it to work. How should I modify the code to get it to work with ARC?
I get that the Block_release() stuff needs to go, but I can't get past the compile errors about casting 'void *' to 'void (^)(NSInteger)' being disallowed with ARC.
ARC doesn’t like conversions to void *
, which is what the Block_* functions expect as arguments, because ARC cannot reason about ownership of non-retainable types. You need to use bridge casts to tell ARC how it should manage the ownership of the objects involved, or that it shouldn’t manage their ownership at all.
You can solve the ARC issues by using the code below:
- (void)beginSheet:(NSWindow *)sheet
modalForWindow:(NSWindow *)docWindow
didEndBlock:(void (^)(NSInteger returnCode))block
{
[self beginSheet:sheet
modalForWindow:docWindow
modalDelegate:self
didEndSelector:@selector(my_blockSheetDidEnd:returnCode:contextInfo:)
contextInfo:Block_copy((__bridge void *)block)];
}
- (void)my_blockSheetDidEnd:(NSWindow *)sheet
returnCode:(NSInteger)returnCode
contextInfo:(void *)contextInfo
{
void (^block)(NSInteger) = (__bridge_transfer id)contextInfo;
block(returnCode);
}
In the first method,
Block_copy((__bridge void *)block)
means the following: cast block
to void *
using a __bridge
cast. This cast tells ARC that it shouldn’t manage the ownership of the operand, so ARC won’t touch block
memory-management-wise. On the other hand, Block_copy()
does copy the block so you need to balance that copy with a release later on.
In the second method,
void (^block)(NSInteger) = (__bridge_transfer id)contextInfo;
means the following: cast contextInfo
to id
(the generic object type in Objective-C) with a __bridge_transfer
cast. This cast tells ARC that it should release contextInfo
. Since the block
variable is __strong (the default qualifier), the Block is retained and, at the end of the method, it is finally released. The end result is that block
gets released at the end of the method, which is the expected behaviour.
Alternatively, you could compile that category with -fno-objc-arc
. Xcode allows files in the same project to build with or without ARC enabled.