通常,块可以是3种类型:NSGlobalBlock,NSStackBlock,NSMallocBlock。让我们看下面的例子:

    void (^aBlock)(NSString *someString) = ^(NSString *someString){
        NSLog(@"Block was executed. %@", someString);
    };
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:aBlock forKey:@"aBlock"];

因为如果我执行 po dictionary ,aBlock不会捕获周围的范围,我得到

aBlock = < NSGlobalBlock :0x165dde60>,这是正确的

如果我那么做:
    NSString *string = @"Test";
    void (^aBlock)(NSString *someString) = ^(NSString *someString){
        NSLog(@"Block was executed. %@ %@", someString, string);
    };
    NSDictionary *dictionary = [NSDictionary dictionaryWithObject:aBlock forKey:@"aBlock"];

然后 po dictionary ,我得到:

aBlock = < NSMallocBlock :0x165dde60> ,这让我感到困惑

这不应该是 NSStackBlock ,并且在执行以下操作时仅成为 NSMallocBlock :
 NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[aBlock copy] forKey:@"aBlock"];

我在使用ARC的iOS 7.1上,据我所知,在向下传递到堆栈时,默认情况下不应在ARC中复制块,而应该仅在向上传递到堆栈(从函数返回)时才复制它们。

我在这里想念什么?

最佳答案

在这些行中,字典中的块对象的类型已经是NSMallocBlock,而不是通过NSDictionary + dictionaryWithObject:forKey:方法复制的。

void (^aBlock)(NSString *someString) = ^(NSString *someString){
    NSLog(@"Block was executed. %@ %@", someString, string);
};

在ARC编译环境下,默认情况下,此aBlock变量为__strong。
__strong void (^aBlock)(NSString *someString) = ^(NSString *someString){
...

因此,块对象由aBlock变量保留。实际上,根据LLVM源代码,编译器发出了保留代码,用于将对象存储到该行的__strong变量中。
  • https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L2091
  • https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L2109
  • https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L1920
  • https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L1944

  • EmitARCRetainBlock:
    llvm::Value *CodeGenFunction::EmitARCRetainBlock(llvm::Value *value, bool mandatory) {
        llvm::Value *result = emitARCValueOperation(*this, value,
            CGM.getARCEntrypoints().objc_retainBlock, "objc_retainBlock");
    

    此objc_retainBlock是objc4中的运行时函数。

    http://opensource.apple.com/source/objc4/objc4-551.1/runtime/NSObject.mm
    id objc_retainBlock(id x) {
        return (id)_Block_copy(x);
    }
    

    因此,此_Block_copy将块对象从堆栈复制到堆。

    除此之外,您还可以使用 __weak 看到块对象的 __NSStackBlock__ 类型。
    __weak void (^aBlock)(NSString *someString) = ^(NSString *someString){
        NSLog(@"Block was executed. %@ %@", someString, string);
    };
    

    在这种情况下,aBlock变量不会保留该块对象,并且该块对象不是普通的Objective-C对象,因此该块对象可以存在于堆栈中。是的,它是 __NSStackBlock__ 对象。在将其存储到NSMutableDictionary中之前,可能需要调用复制 Block_copy

    09-04 17:33