【问题标题】:Compactly disable arc-retain-cycles warning for self-referencing blocks紧凑地禁用自参考块的 arc-retain-cycles 警告
【发布时间】:2012-01-05 06:34:53
【问题描述】:

我正在编写一个涉及事件处理的 API,我希望能够为处理程序使用块。回调通常会想要访问或修改 self.在 ARC 模式下,Clang 警告说,引用 self 的块可能会创建一个保留循环,这似乎是一个有用的警告,我想继续保持下去。

但是,对于我的 API 的这一部分,回调和包含对象的生命周期是在外部维护的。我知道我可以在应该释放对象时打破循环。

我可以使用#pragma clang diagnostic ignored "-Warc-retain-cycles" 关闭每个文件的保留周期警告,但这会禁用整个文件的警告。我可以用#pragma clang diagnostic pushpop 围绕该警告将块包围,但这会使块变得丑陋。

我也可以通过引用指向 self 的 __weak 变量而不是直接引用 self 来消除警告,但这会使块的使用变得不那么愉快。

我想出的最佳解决方案是这个宏,它可以在块周围进行诊断禁用:

#define OBSERVE(OBJ, OBSERVEE, PATH, CODE) \
[(OBJ) observeObject:(OBSERVEE) forKeyPath:(PATH) withBlock:^(id obj, NSDictionary *change) { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-retain-cycles\"") \
do { CODE; } while(0); \
_Pragma("clang diagnostic pop") \
}];

这行得通,但对于 API 用户来说不是很容易发现,它不允许嵌套观察者,并且它与 XCode 的编辑器的交互很差。有没有更好的方法来禁用或避免警告?

【问题讨论】:

  • 创建对self__weak 引用实际上只需要一行代码。我认为在这种情况下解决问题比试图缓解症状要好。引用 weakSelf 而不是 self 如何使块的使用变得不那么愉快?
  • 这在几个方面不太令人愉快。听众通常很短,有时是一个单一的陈述。 __weak 声明使侦听器的大小加倍。这也意味着您需要限定属性访问而不是使用推断的自我。我同意我目前的解决方案可能比仅使用 __weak 更糟糕,但我希望通过这个问题得到一个更好的解决方案。
  • 你能改变你的完成块的原型来接受一个“自我”参数吗?现在你传递你的块的代码看起来是一样的(除了接受一个额外的参数),你可以消除警告。 (即让您的 API 将相关对象传递给您的块)
  • 另外,一些代码示例在这里可能很好
  • self 没有在足够多的块中使用,因此为其添加签名会引入更多噪音。我已经开始接受 Mark 的立场,即声明 __weak 引用 self 比所有这些为摆脱它而争吵要邪恶。如果他想添加他的评论作为答案,我会将其标记为已接受。

标签: objective-c objective-c-blocks clang automatic-ref-counting


【解决方案1】:

为了解决创建弱引用的笨拙问题,我将其放入宏中。它使用预处理器创建一个具有相同名称但带有前缀的新 var(在本例中为“w”;我避免使用“弱”,因为这会过度杀伤力,并且会更加混乱大写规则):

#define WEAK_VAR(NAME) __unsafe_unretained typeof(NAME) w##NAME = NAME

...
WEAK_VAR(self);
self.block = ^{
    [wself doStuff];
};

如果,otoh,弱引用是不可取的,不要使用它!我喜欢 nielsbot 将对象作为参数传递的解决方案(当然,如果可能的话)。

【讨论】:

    【解决方案2】:

    我编写了以下宏,我认为非常聪明...

    #define CLANG_IGNORE_HELPER0(x) #x
    #define CLANG_IGNORE_HELPER1(x) CLANG_IGNORE_HELPER0(clang diagnostic ignored x)
    #define CLANG_IGNORE_HELPER2(y) CLANG_IGNORE_HELPER1(#y)
    
    #define CLANG_POP _Pragma("clang diagnostic pop")
    #define CLANG_IGNORE(x)\
        _Pragma("clang diagnostic push");\
        _Pragma(CLANG_IGNORE_HELPER2(x))
    

    它可以让你做各种有趣的事情(没有 Xcode 骚扰你),例如..

    CLANG_IGNORE(-Warc-retain-cycles)
    [object performBlock:^(id obj){ [obj referToSelfWithoutWarning:self]; }];
    CLANG_POP
    

    您可以设置任何警告标志,Clang 会听从您的一时兴起...

    CLANG_IGNORE(-Warc-performSelector-leaks);
    return [self performSelector:someIllBegotSelector withObject:arcFauxPas];
    CLANG_POP
    

    再一次,警告通常是有原因的。派对爆米花。

    【讨论】:

      【解决方案3】:

      我认为禁用警告是目前唯一正确的方法,因为它说编译器:不要关心这个保留周期,我知道它,我会自己处理观察者。 引入弱引用是一种代价高昂的解决方案,因为它会带来运行时 CPU 和内存开销。

      【讨论】:

        【解决方案4】:

        新的 LLVM 更擅长检测/防止此类保留周期 等待 LLVM 与 ios6 一起发布,或者按照 alex 的方式创建一个弱 var。

        虽然禁用警告是个坏主意!

        【讨论】:

          【解决方案5】:

          首先,有一种简单的方法可以使用#pragma 禁用某些代码行的警告:

          #pragma clang diagnostic push
          #pragma clang diagnostic ignored "<#A warning to ignore#>"
          <#Code that issues a warning#>
          #pragma clang diagnostic pop
          

          但我不会在这种特殊情况下使用它,因为它不会解决问题,它只会对开发人员隐藏它。 我宁愿采用马克提出的解决方案。要创建弱引用,您可以在块外执行以下操作之一:

          __weak typeof(self) weakSelf = self; // iOS ≥ 5
          __unsafe_unretained typeof(self) unsafeUnretainedSelf = self; // 5 > iOS ≥ 4
          __block typeof(self) blockSelf = self; // ARC disabled
          

          【讨论】:

            猜你喜欢
            • 2012-02-09
            • 1970-01-01
            • 2011-08-17
            • 2019-11-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-04
            • 1970-01-01
            相关资源
            最近更新 更多