【问题标题】:NSDictionary and Objective-C block quirkNSDictionary 和 Objective-C 块怪癖
【发布时间】:2020-06-09 00:51:25
【问题描述】:

我用键 NSString 和对象块初始化一个 NSDictionary,就像这样。

NSDictionary * d =
[NSDictionary dictionaryWithObjectsAndKeys:
  ^ ( int p1 ){ some code }, @"a",
  ^ ( int p1, NSString * p2 ){ some code }, @"b",
nil];

当我检索其中一些块时,它检索失败,即

someVar = [d objectForKey:@"b"];

即使有与 @"b" 关联的对象也会失败。

当我记录字典时,我注意到我可以检索的对象存储为__NSMallocBlock__,而那些失败的对象存储为__NSStackBlock__。尽管__NSStackBlock__ 似乎有效,但调试器显示它包装了nil 块。

编辑

这很疯狂有两个原因。

首先,我无法仅使用上面的 sn-ps 生成错误。但是,如果块还包含对任何弱指针的引用,则会导致错误。

从而产生你需要的错误

__weak NSString * p = @"ab"; // Some weak pointer
NSDictionary * d =
[NSDictionary dictionaryWithObjectsAndKeys:
  ^ ( int p1 ){ some code }, @"a",
  ^ ( int p1, NSString * p2 ){ NSLog( @"%p", p ); }, @"b",
nil];

虽然前面给出的 sn-p 没有任何问题。

我已经尝试了失败的 sn-p,它带有一个指向 self 或上述任意字符串的弱指针,但都失败了。请注意,即使指针无效,日志也应该可以正常工作。

其次,如上所述,当您检索对象时会发生错误!即使我对块什么都不做,只是从字典中检索它,我会得到 EXC_BAD_ACCESS 崩溃。

【问题讨论】:

    标签: objective-c objective-c-blocks


    【解决方案1】:

    这与块参数有关。

    编译器和 ARC 根据块的参数以不同的方式存储块。为了解决这个问题,创建指向块的强指针,然后将这些指针存储在字典中,因此

    blockVarA = ^ ( pars ){ some code };  // Strong pointers
    blockVarB = ^ ( pars ){ some code };
    
    NSDictionary * d =
    [NSDictionary dictionaryWithObjectsAndKeys:
      blockVarA, @"a",
      blockVarB, @"b",
    nil];
    

    将确保所有内容都存储为__NSMallocBlock__ 块。

    【讨论】:

      【解决方案2】:

      唉,Apple 块处理中的另一个错误,我们认为他们在多年前就已经修复了。

      首先在网站 feedbackassistant.apple.com 上报告您的原始代码 - 他们修复它的可能性很小,但无论如何请这样做。 (请随意包含以下代码作为正确处理的版本。)

      其次你可以使用更现代的代码:

      NSDictionary * d = @{ @"a" : ^ ( int p1 ){ NSLog(@"%d", p1 * arg); },
                            @"b" : ^ ( int p1, NSString * p2 ){ NSLog(@"%d, '%@'"
                          };
      

      Apple 确实可以正确编译(通过查看底层机制和类型,可以看出为什么这与您的代码不同并且没有相同的编译器错误)。

      后记

      您是对的,Apple 确实会根据某些因素以不同的方式存储块 - 这是一种编译器优化,与所有此类优化一样,程序员在改进的代码(性能、大小等)中不应该看到它。

      然而,由于 Apple 从未解释的原因,他们决定发布对程序员可见的这种优化的块实现,并且确实要求程序员添加特定的代码来处理它。随着时间的推移,他们不再需要添加该代码,并最终记录了“工作完成”。

      在同样使用 ARC 的 Swift 中,他们似乎从使用 Objective-C 的经验中吸取了教训,并通过优化和语言级别注释的组合正确地实现了块处理——人们可能会就必要性/好处/成本/选择/等。后者,但我们可以说 Apple 在做出选择方面并不是独一无二的,而且与他们从 Objective-C 中的块开始的地方相比,这肯定是一个巨大的改进。

      【讨论】:

      • 感谢 CRD!苹果从不解释......特别是如果现代方法有效,我认为这将获得 0 胃口。但是,现代方法和我的方法不同这一事实表明,在某个地方仍有一些工作要做。我知道 Swift 使用 ARC,并且非常惊讶这在 Swift 中有效。
      • @CRD 评论已删除。你是对的,我实际上把两件事混在一起,我错了。
      • @CRD 这打开了一罐蠕虫(对我来说)......我有很多问题,但让我尽可能简短。我同意 id 在 ARC 下不应该要求 copy。但问题是针对 Apple 还是针对 clang 的收件箱?如果id 派生自NSObject 我会把它交给Apple,但是块指针应该去clang?似乎id 被包裹在__NSMallocBlock__ 中,我认为它源自NSObject。甚至NSObject 也参与其中,这似乎更多的是编译器/clang 领域。
      • 我重新阅读了ARC - Blocks 文档: 当一个块指针类型转换为非块指针类型(例如id)时,Block_copy 被调用。 ...当块指针作为可变参数传递给函数时,它会隐式转换为id绝对是一个错误。
      • @skaak - 您将错误发送到 feedbackassistant.apple.com,clang 本质上是 Apple 的编译器,他们编写并发布了 Objective-C(和 Swift)编译器,并设计和实现了 ARC 和块规范.
      猜你喜欢
      • 2020-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多