Why do NSBlocks have to be copied for storage in containers?

前端 未结 2 796
心在旅途
心在旅途 2020-12-16 08:19
- (void) addABlock 
{
void (^aBlock)(void) = ^() { [someObject doSomething]; };

[self.myMutableArray addObject: aBlock];  // Oops..

[self.myMutableArray addObject:         


        
2条回答
  •  时光取名叫无心
    2020-12-16 09:00

    Update

    Despite what Apple documentation says:

    Blocks “just work” when you pass blocks up the stack in ARC mode, such as in a return. You don’t have to call Block Copy any more. You still need to use [^{} copy] when passing “down” the stack into arrayWithObjects: and other methods that do a retain.

    it's no longer necessary to manually call copy on a block when adding it to a container. The lack of automatic copy in this case has been considered a compiler bug and fixed in llvm long time ago.

    "We consider this to be a compiler bug, and it has been fixed for months in the open-source clang repository."

    (John McCall, LLVM developer)

    I personally tested this in Xcode 5, using the latest Apple LLVM 5.0 compiler.

    - (NSArray *)blocks {
        NSMutableArray *array = [NSMutableArray array];
        for (NSInteger i = 0; i < 3; i++) {
            [array addObject:^{ return i; }];
        }
        return array;
    }
    
    - (void)test {
        for (NSInteger (^block)() in [self blocks]) {
            NSLog(@"%li", block());
        }
    }
    

    The above example correctly prints

    0
    1
    2
    

    under ARC and it crashes with EXC_BAD_ACCESS in MRC.

    Note that this is - finally - coherent with the llvm documentation, which states

    whenever these semantics call for retaining a value of block-pointer type, it has the effect of a Block_copy

    meaning that whenever ARC has to retain a pointer and this pointer happens to be a block-pointer type, Block_copy will be called instead of retain.


    Original answer

    The part that I do not understand is why I have to manually call copy.

    Blocks are one of the few examples of Objective-C objects allocated on the stack (for performance reasons), so when you return from the method call you lose them, due to the tear down of the current stack frame.

    Sending copy on a stack-block will call Block_copy on it and it will move it on the heap, allowing you to keep a valid reference to the block.

    So why doesn't [NSBlock retain] simply call through to [NSBlock copy]

    This would break the usual semantic of retain, which is supposed to return the object itself, with an incremented retain count. Since incrementing a retain count on an stack-block doesn't make any sense, calling retain on a stack-block doesn't have any effect.

    Apple could have implemented it differently, as you suggest, but they preferred to stick as much as possible to the common contracts of memory management methods.

    As a further reference on blocks, you may want to have a look at this great blog post by @bbum. It's pre-ARC but the majority of concepts hasn't changed.

提交回复
热议问题