【问题标题】:Lifetime of weak local variables with ARCARC 弱局部变量的生命周期
【发布时间】:2012-02-08 22:54:36
【问题描述】:

如果我有一段看起来像这样的代码:

- (void)testSomething
{
  __weak NSString *str = [[NSString alloc] initWithFormat:@"%@", [NSDate date]];
  NSLog(@"%@", str);
}

输出将是(null),因为没有对 str 的强引用,并且在我分配它后它会立即释放。这是有道理的,并在过渡到 ARC 指南中详细说明。

如果我的代码如下所示:

- (void)testSomething
{
  __weak NSString *str = [NSString stringWithFormat:@"%@", [NSDate date]];
  NSLog(@"%@", str);
}

然后它会正确打印出当前日期。显然,您希望它在非 ARC 世界中工作,因为 str 将被自动释放,因此在此方法退出之前有效。但是,在启用 ARC 的代码中,人们通常认为这两种形式(stringWithFormatalloc/initWithFormat)是等价的。

所以我的问题是,像第二个示例这样的代码是否可以保证在 ARC 下工作。也就是说,如果我对通过我们通常认为的自动释放便利构造函数获得的对象有一个弱引用,那么 保证 在我通常会的相同范围内使用该引用是安全的吗没有ARC(即直到方法退出)?

【问题讨论】:

  • 有趣的问题,但我怀疑最好的答案是“你不应该关心”。
  • 你可能是对的。这个问题实际上只是为了确保我理解我所看到的其他一些问题的答案。
  • UIAdam,我和凯文一起做这个。让编译器担心内存分配。 __weak 是控制实例生命周期的一种有些无效的方法。如果您明确想要控制项目的生命周期,则使用分配给强引用的+alloc 表单。然后在退出时取消该引用。 Nil-ling 引用是新的-release。 IMO,__weak 用于打破保留周期,仅此而已。安德鲁 P.S.有了 ARC,我们都应该“躺下来想想英格兰”。
  • 是的,我同意...我在问这个问题时想到的真实代码实际上确实涉及打破保留周期。

标签: objective-c automatic-ref-counting


【解决方案1】:

自动释放和分配的约定在 ARC 的世界中仍然适用。唯一的区别是 ARC 将插入额外的保留/释放调用,以使泄漏对象或访问已解除分配的对象变得更加困难。

在这段代码中:

__weak NSString *str = [[NSString alloc] initWithFormat:@"%@", [NSDate date]];

对象保留(或等效)的唯一位置是分配。 ARC 会自动插入一个释放命令,导致它立即被释放。

同时,在这段代码中:

 __weak NSString *str = [NSString stringWithFormat:@"%@", [NSDate date]];

按照惯例,像这样的便利构造函数的返回值必须是一个自动释放的对象*。这意味着当前的 autoreleasepool 已经保留了该对象,并且在池耗尽之前不会释放它。因此,您几乎可以保证该对象将至少在您的方法的持续时间内存在 - 尽管您可能不应该依赖这种行为。

(*或以其他方式保留)

【讨论】:

  • 但是请注意,如果您有 NSString *str = [NSString stringWithFormat:@"%@", [NSDate date]]; __weak NSString *weakStr = str; 并且在分配给 weakStr 之后不再引用 str(因此超出范围),那么 weakStr 可能 最终为零,取决于+stringWithFormat: 本身是否在 ARC 下编译。
  • 在 ARC 中,不能保证便利构造函数的返回值最终会出现在自动释放池中。有关详细信息和示例,请参阅我的答案。
【解决方案2】:

根本无法保证局部弱变量的生命周期。如果变量指向的对象被释放,那么之后弱变量将指向nil

如果您对通过不返回保留对象的方法获得的对象有弱引用,则假设该对象在方法退出之前一直存在是安全的。如果您想确保对象存在,请使用强引用。

这里有一个例子说明非保留方法的返回值不能保证在自动释放池中结束:

  • 创建一个新的 iOS 项目(使用 ARC 和 Storyboards 的单一视图应用程序)
  • 将此方法添加到AppDelegate.m:

    + (id)anObject
    {
        return [[NSObject alloc] init];
    }
    
  • 替换-application:didFinishLaunchingWithOptions:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        __weak id x = [AppDelegate anObject];
        NSLog(@"%@", x);
        return YES;
    }
    
  • 重要提示:现在将 Debug 的优化级别设置为 -Os

在本例中,+[AppDelegate anObject] 的作用类似于便利构造函数,但如果您在具有 -Os 优化的设备上执行它,您会看到记录了 (null)。这样做的原因是一个漂亮的 ARC 优化,它可以防止将对象添加到自动释放池中的开销。

您可能已经注意到我切换到不使用像 +[NSString stringWithFormat:] 这样的库方法。这些似乎总是将对象放入自动释放池中,这可能是出于兼容性原因。

【讨论】:

  • 当然,如果不调用自动释放,对象不会自动进入自动释放池。从未如此。但是在实际代码中,如果你有一个方便的构造函数,你希望/需要与现有约定保持一致(即返回的对象是自动释放的),那么你会在它上面调用 autorelease ,因此它会表现为一个自动释放的对象,无论是否您是否使用 ARC。
  • 其实对于上面的-anObject方法,ARC会自动插入对objc_autoreleaseReturnValueobjc_returnAutoreleaseReturnValue的调用。如果从非 ARC 代码或什至在 ARC 中调用 -anObject,该对象将最终进入自动释放池,除非您设置优化级别 -Os。
  • 而且自ARC以来,约定是不将对象添加到自动释放池中,而是尝试进行优化,以便不需要添加到自动释放池中。添加到自动释放池被描述为“最坏情况”。见clang.llvm.org/docs/…。所以你的第二个例子在 ARC 下不能保证——它现在可以工作,但不能保证。
猜你喜欢
  • 2013-02-17
  • 2018-12-14
  • 2020-12-31
  • 1970-01-01
  • 1970-01-01
  • 2011-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多