ARC下弱引用局部变量的生命周期

5

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

- (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的情况下相同的范围内使用该引用是否保证是安全的(即直到该方法退出)?

1
有趣的问题,但我认为最好的答案是“你不应该在意”。 - Lily Ballard
你可能是对的。这个问题只是为了确保我理解了我看到的其他问题的答案。 - UIAdam
UIAdam,我和Kevin的想法一致。让编译器去担心内存分配吧。__weak是一种相对无效的控制实例生命周期的方法。如果您明确想要控制项目的生命周期,请使用分配给强引用的+alloc形式。然后在退出时将该引用设置为nil。将引用设置为nil是新的-release。在我看来,__weak仅用于打破保留循环,没有其他作用。安德鲁 P.S.有了ARC,我们应该都可以“放松身心,想着英格兰”。 - adonoho
是的,我同意...实际上,当我提出这个问题时,我心中想到的真正代码确实涉及到打破保留循环。 - UIAdam
2个回答

5

自动释放和分配的约定在ARC环境中仍然适用。唯一的区别是,ARC会插入额外的retain/release调用,以使对象泄漏或访问已释放的对象更加困难。

在这段代码中:

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

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

同时,在这段代码中:

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

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

(* 或以其他方式保留)


1
请注意,如果您有NSString *str = [NSString stringWithFormat:@"%@", [NSDate date]]; __weak NSString *weakStr = str;并且如果在分配给weakStr之后不再引用str(因此超出范围),则weakStr可能会变成nil,这取决于+stringWithFormat:本身是否在ARC下编译。 - Lily Ballard
在ARC中,便利构造函数的返回值不能保证最终会进入自动释放池。请参阅我的答案以获取详细信息和示例。 - Tammo Freese

1

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

如果您对通过不返回保留对象的方法获得的对象有一个弱引用,则不能安全地假定该对象存在于方法退出之前。如果要确保对象存活,请使用强引用。

以下是一个示例,说明非保留方法的返回值不能保证最终进入自动释放池:

  • 创建一个新的iOS项目(使用ARC和Storyboards的Single View App)
  • 将以下方法添加到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,它就不会自动进入autorelease池中。这从来没有发生过。但是在实际代码中,如果你有一个方便的构造函数,你希望/需要与现有约定一致(即返回的对象是自动释放的),那么你应该在其上调用autorelease,因此无论你是否使用ARC,它都将表现为自动释放的对象。 - UIAdam
实际上,对于上面的-anObject方法,ARC会自动插入一个调用objc_autoreleaseReturnValueobjc_returnAutoreleaseReturnValue的语句。如果从非ARC代码中调用-anObject,对象将最终进入自动释放池,即使在ARC中也是如此,除非您设置了优化级别-Os。 - Tammo Freese
自从ARC出现以来,约定是不将对象添加到自动释放池中,而是尝试进行优化,以避免必须添加到自动释放池中。将对象添加到自动释放池中被描述为“最坏情况”。请参见http://clang.llvm.org/docs/AutomaticReferenceCounting.html#objects.operands.other-returns。因此,在ARC下,您的第二个示例不能保证可行 - 它现在可能有效,但不能保证。 - Tammo Freese

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接