beginSheet: block alternative with ARC?

2019-04-08 08:45发布

问题:

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.

回答1:

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.