你能帮我理解添加到容器中的块类型(NSDictionary,NSArray)吗?

Hor*_*hiv 2 memory-management objective-c nsdictionary objective-c-blocks automatic-ref-counting

通常块可以有3种类型:NSGlobalBlock,NSStackBlock,NSMallocBlock.让我们举个例子:

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

由于ABLOCK不捕获周围的范围,如果我做PO字典,我得到

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"];
Run Code Online (Sandbox Code Playgroud)

然后po字典,我得到:

aBlock = < NSMallocBlock:0x165dde60> 这就是我的困惑

这不应该是一个NSStackBlock,只有当我这样做时才成为NSMallocBlock:

 NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[aBlock copy] forKey:@"aBlock"];
Run Code Online (Sandbox Code Playgroud)

我使用ARC在iOS 7.1上,据我所知,在向下传递堆栈时,不应该在ARC中默认复制块,并且只有在向上传递堆栈(从函数返回)时才应复制它们.

我在这里错过了什么?

Kaz*_*oto 8

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

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

在ARC编译环境下,此aBlock变量是__strong默认值.

__strong void (^aBlock)(NSString *someString) = ^(NSString *someString){
...
Run Code Online (Sandbox Code Playgroud)

所以块对象由aBlock变量保留.实际上,根据LLVM源代码,编译器发出了保留代码,用于将对象存储到行上的__strong变量中.

  1. https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L2091
  2. https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L2109
  3. https://github.com/llvm-mirror/clang/blob/master/lib/CodeGen/CGObjC.cpp#L1920
  4. 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");
Run Code Online (Sandbox Code Playgroud)

这个objc_retainBlockobjc4中的运行时函数.

http://opensource.apple.com/source/objc4/objc4-551.1/runtime/NSObject.mm

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
Run Code Online (Sandbox Code Playgroud)

因此,通过此_Block_copy将块对象从堆栈复制到堆.

除此之外,您还可以使用__weak查看块对象的__NSStackBlock__类型.

__weak void (^aBlock)(NSString *someString) = ^(NSString *someString){
    NSLog(@"Block was executed. %@ %@", someString, string);
};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,aBlock变量不保留块对象,并且块对象不是普通的Objective-C对象,因此块对象可以存在于堆栈中.是的,它是__NSStackBlock__对象.在存储到NSMutableDictionary之前,您可能需要为它调用copyBlock_copy.