【问题标题】:Is `typeof(self) self = weakSelf` construction legitimate inside block?`typeof(self) self = weakSelf` 构造在块内是否合法?
【发布时间】:2013-01-07 11:04:11
【问题描述】:

我想知道问题主题中的变量声明是否合法。想象一下下面的代码:

__weak typeof(self) weakSelf = self;
[self doSomethingThatMayCauseRetainCycleWithBlock:^{
    typeof(self) self = weakSelf; // <---- !!!!
    if (self == nil) return;

    NSAssert(self.someProperty != nil, @"This doesn't lead to retain cycle!");

    [self doSomething];
    self.someProperty = someValue;

    // even
    self->someIvar = anotherValue;
}

此代码在 Xcode 4.5.2 中完美运行,仅发出警告 Declaration shadows a local variable

这个怪癖有什么意义:

  1. self 重新声明为对弱变量的强引用后,您可以安全地在块内部/外部复制/移动代码,而不会冒偶尔创建保留循环的风险(除了 ivars,但它们是邪恶的)。
  2. 块中的NSAssert 不再导致保留循环。

更新 我发现这种技术在libextobjc 中用于@weakify/@strongify 宏。

【问题讨论】:

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


【解决方案1】:

这个答案分为两部分:

  1. 声明掩盖了局部变量警告GCC_WARN_SHADOW的根本原因,以及为什么打开它可能是一个糟糕的警告。
  2. 构建一个不需要这种诡计的 NSAssert 的替代品

GCC_WARN_SHADOW

您收到的警告来自GCC_WARN_SHADOW,这是最无用的警告之一(无论如何,在我看来)。编译器在这里做的事情是正确的,但是感谢GCC_WARN_SHADOW,它会引起您的注意可能您正在做一些超出您预期的事情。

这是偏执的编译器警告擅长的事情。缺点是很难(并非不可能)删除特定警告以表明您知道自己在做什么。

启用GCC_WARN_SHADOW,此代码将生成警告:

int value = MAX(1,MAX(2,3));

尽管如此,它会完美运行。

这样做的原因是它编译成这样,虽然丑陋但(对编译器而言)非常清楚:

({
   int __a = (1);
   int __b = (({
       int __a = (2);
       int __b = (3);
       __a < __b ? __b : __a;
   }));
   __a < __b ? __b : __a;
})

所以你的提议会很好用。 NSAssert 是使用使用 self 变量的宏实现的。如果您在作用域中定义 self,它会取而代之的是 self

替换 NSAssert

话虽如此,如果您认为GCC_WARN_SHADOW 有用,还有另一种解决方案。无论如何,它可能会更好:提供您自己的宏。

#define BlockAssert(condition, desc, ...) \
    do { \
        __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
        if (!(condition)) { \
            [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
            object:strongSelf file:[NSString stringWithUTF8String:__FILE__] \
                lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
        } \
        __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
    } while(0)

这本质上是NSAssert 的复制粘贴,但它使用strongSelf 而不是self。在你use the strongSelf pattern 的地方,它会起作用;在未定义 strongSelf 的地方,您会收到编译器错误:Use of undeclared identifier: 'strongSelf'。我认为这是一个很好的提示。

【讨论】:

  • 对于那些阅读此出色答案但不了解所指的“strongSelf”模式的人,请参阅此帖子及其 cmets:stackoverflow.com/a/17105368/796419
  • 良好的链接。我已将其添加到答案中。 :)
  • 实际上,GCC_WARN_SHADOW 警告可以发现一些众所周知的难以发现的错误。这是一个我不想错过的警告,我不会因为一些轻微的不便而放弃。
  • 真的吗?由于 GCC_WARN_SHADOW 直到最近,这给了编译器警告(我认为它没有被修复,尽管它可能已经修复):MIN(3,MAX(1,4))。更不用说强迫您在块中以不同的方式命名错误变量,如果您尝试将代码移入和移出块,这将导致错误。这是不好的警告。
【解决方案2】:

严格来说,您的代码没有任何问题——变量声明是合法的。但是,您可能会收到关于局部变量隐藏实例变量的编译器警告。

GCD 块实际上是 C 函数,而不是 Objective-C 方法。编译时,每个 Objective-C 实例方法都添加了一个额外的参数,即self 指针。 self 不像其他变量那样存储在对象结构中。

出于这个原因,我会犹豫在我要共享的库中使用此代码。代码可能会与较新版本的 then 编译器中断,因为您实际上对运行时的攻击比立即显而易见的要多一点。此外,正如您所指出的,它是古怪的代码:) 我不确定其他阅读它的人会立即理解发生了什么。

【讨论】:

  • 为什么会有风险?基本上你所做的只是依赖可变阴影;如果您打开了 GCC_WARN_SHADOW,您将收到一条警告消息。但可变阴影是一种合法的技术,并且经过充分理解和测试。
  • 我从来没有说过有风险——我说过我会犹豫这样做,因为它可能会在未来崩溃并且其他开发人员可能会误解它。也许我的第一点是无效的。
  • 我认为唯一真正取决于 NSAssert 直接访问 self 的定义。也就是说,自从发布这个我想到了一个更好的方法来做到这一点:定义你自己的断言宏。不需要其他开发人员可能会误解的东西。 :)
  • 块既不是 C 函数也不是 objc 方法——它们是在 C 函数之上实现的,但 objc 方法也是如此。它们本身就是一个类,因为它们也可以通过objective-c消息调用(-invoke
  • 没错。不过,NSAssert 会捕获 self 而不是让 self 代表块。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
  • 2011-06-28
相关资源
最近更新 更多