【问题标题】:Could you help me to understand block types when added to containers (NSDictionary, NSArray)?添加到容器(NSDictionary、NSArray)时,您能帮我理解块类型吗?
【发布时间】:2014-11-05 19:22:48
【问题描述】:

通常块可以有 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 不会捕获周围的范围,我会得到 ​​p>

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

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

我错过了什么?

【问题讨论】:

  • 我不明白您如何能够将 stack 分配的内存添加到字典中。要使块持续存在,它需要在堆上。无论如何,我的猜测是,在这种情况下,NSMallocBlock 是在编译时确定的,因为编译器可以看到在堆栈框架之外需要它。
  • 嗨,您对堆栈与堆的评论是有效的,我的困惑来自这样一个事实,即文档明确指出,即使在 ARC 下,当我们向字典中添加本地范围的块时,我们也必须复制它(基本上将它移动到堆中),在当前情况下,根据文档它不应该存在时,块已经在堆中。也许文档已经过时了,但是直到我确定某些事情之前,我才考虑使用 NSMapTable 并将其设置为复制其值。对我来说,这似乎是最安全的方法。
  • 您可以编辑您的帖子以包含该信息吗?知道会很有帮助。 (特别是您引用的文档的链接)。我认为这不是您试图在本地范围之外保留的字典吗?

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


【解决方案1】:

字典中块对象的类型在这些行上已经是 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 源码,编译器发出 retain 代码,用于将对象存储到 __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");

这个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 之前,您可能需要调用 copyBlock_copy

【讨论】:

  • 感谢您的回复。你是对的,出于某种原因,我查看了一个过时的 Transitioning to ARC Release Notes 文档,其中说:“当你在 ARC 模式下将块向上传递到堆栈时,块“正常工作”,例如在返回中。你没有不必再调用 Block Copy。在将堆栈“向下”传递到 arrayWithObjects: 和其他执行保留的方法时,您仍然需要使用 [^{} copy]。我再次查找了该文档,在当前版本中,我发现在网上缺少最后一句话,所以我想在 ARC 下我不必再担心这个了,即使它并不总是这样。
猜你喜欢
  • 1970-01-01
  • 2018-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多