objective-c - 你能帮我理解添加到容器(NSDictionary、NSArray)时的 block 类型吗?

标签 objective-c memory-management automatic-ref-counting nsdictionary objective-c-blocks

通常 block 可以是 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字典,我得到:

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

这不应该是一个 NSStackBlock 并且只在我这样做时才变成一个 NSMallocBlock:

 NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[aBlock copy] forKey:@"aBlock"];

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

我在这里错过了什么?

最佳答案

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

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

这个aBlock变量在ARC编译环境下默认是__strong

__strong void (^aBlock)(NSString *someString) = ^(NSString *someString){
...

所以block对象被aBlock变量保留了下来。实际上,根据 LLVM 源代码,编译器发出了用于将对象存储到 __strong 变量中的 retain 代码。

  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");

这个 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 对象被这个_Block_copy从栈复制到堆。

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

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

在这种情况下, block 对象并没有被aBlock变量保留, block 对象也不是普通的Objective-C对象,所以 block 对象可以存在于栈中。是的,它是 __NSStackBlock__ 对象。在存储到 NSMutableDictionary 之前,您可能需要调用 copyBlock_copy

关于objective-c - 你能帮我理解添加到容器(NSDictionary、NSArray)时的 block 类型吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25794306/

相关文章:

objective-c - 调整 UIButton 上的图像

c++ - 设置数组元素时发生 MSVC 访问冲突

ios - UITableView reloadData EXC_BAD_ACESS code=2

ios - iphone 应用程序终止时删除文档文件夹中的文件

ios - 调用了 deinit 但对象仍在内存中

ios - 光线模糊效果?

objective-c - 我如何 "freeze"与其他窗口进行所有交互?

objective-c - 多任务iPhone SDK uibackgroundmodes无法与音频一起使用吗?

ios - AFNetworking CFData 泄漏?

memory-management - Forth 中的内存管理