为什么我的弱引用在强引用消失之后就被清除了?

28

我有点固执,但我想要很好地理解弱引用和强引用,这就是为什么我再次向你请教的原因。

考虑以下情况:

__weak NSString* mySecondPointer = myText;   
NSLog(@"myText: %@", myText);

结果是myText:(null),这很显然——在赋值后立即将弱引用设置为null,因为没有指向该对象的强引用。

但在这种情况下:

__strong NSString* strongPtr = [[NSString alloc] initWithFormat:@"mYTeSTteXt %d"]; 
// weak pointer points to the same object as strongPtr
__weak NSString* weakPtr = strongPtr;
if(strongPtr == weakPtr) 
     NSLog(@"They are pointing to the same obj");        
NSLog(@"StrongPtr: %@", strongPtr);
NSLog(@"weakPtr: %@", weakPtr);

NSLog(@"Setting myText to different obj or nil");

// after line below, there is no strong referecene to the created object:
strongPtr = [[NSString alloc] initWithString:@"abc"];  // or myText=nil;

if(strongPtr == weakPtr) 
     NSLog(@"Are the same");
else
     NSLog(@"Are NOT the same");
NSLog(@"StrongPtr: %@", strongPtr);
// Why weak pointer does not point to nul
NSLog(@"weakPtr: %@", weakPtr);

输出:

2013-03-07 09:20:24.141 XMLTest[20048:207] They are pointing to the same obj
2013-03-07 09:20:24.142 XMLTest[20048:207] StrongPtr: mYTeSTteXt 3
2013-03-07 09:20:24.142 XMLTest[20048:207] weakPtr: mYTeSTteXt 3
2013-03-07 09:20:24.143 XMLTest[20048:207] Setting myText to different obj or nil
2013-03-07 09:20:24.143 XMLTest[20048:207] Are NOT the same
2013-03-07 09:20:24.144 XMLTest[20048:207] StrongPtr: abc
2013-03-07 09:20:24.144 XMLTest[20048:207] weakPtr: mYTeSTteXt 3   // <== ??

我的问题:

为什么在 strongPtr = [[NSString alloc] initWithString:@"abc"]; 之后,弱指针的值没有更改为 nil(为什么一开始创建的对象仍然存在于内存中,尽管它没有任何强引用?--或许它有吗?)


我尝试了这个解决方法:(但我不确定是否适合添加注释)。我已经在 @autorealesepool 中包含了创建 strongPtr 的代码。我不确定它是否是正确的解决方案,但它可以正常工作...

 __strong NSString* strongPtr;
    __weak NSString* weakPtr;
    @autoreleasepool {


        strongPtr = [[NSString alloc] initWithFormat:@"mYTeSTteXt %d", 3];

        // weak pointer point to object create above (there is still strong ref to this obj)
        weakPtr = strongPtr;
        if(strongPtr == weakPtr) NSLog(@"They are pointing to the same obj");        

        NSLog(@"StrongPtr: %@", strongPtr);
        NSLog(@"weakPtr: %@", weakPtr);

        NSLog(@"Setting myText to different obj or nil");   

    // after line below, there is no strong referecene to the created object:
     strongPtr = [[NSString alloc] initWithString:@"abc"];  


    }

    if(strongPtr == weakPtr) 
        NSLog(@"Are the same");
    else
        NSLog(@"Are NOT the same");
    NSLog(@"StrongPtr: %@", strongPtr);
    // Why weak pointer does not point to nul
    NSLog(@"weakPtr: %@", weakPtr);

输出:

2013-03-07 09:58:14.601 XMLTest[20237:207] They are pointing to the same obj
2013-03-07 09:58:14.605 XMLTest[20237:207] StrongPtr: mYTeSTteXt 3
2013-03-07 09:58:14.605 XMLTest[20237:207] weakPtr: mYTeSTteXt 3
2013-03-07 09:58:14.606 XMLTest[20237:207] Setting myText to different obj or nil
2013-03-07 09:58:14.607 XMLTest[20237:207] Are NOT the same
2013-03-07 09:58:14.607 XMLTest[20237:207] StrongPtr: abc
2013-03-07 09:58:14.608 XMLTest[20237:207] weakPtr: (null)

4
非常好的问题示例! - James Webster
5
@FruityGeek,你能否解释一下问题,而不只是做聪明的评论吗? - zoul
@FruityGeek:还有其他提示吗? - radekEm
2
if(strongPtr == weakPtr)NSLog(@"weakPtr: %@", weakPtr);两段代码似乎都会创建临时/自动释放的引用。如果你移除这些行,那么在结尾处weakPtr会被设为nil,与预期相符。 - Martin R
4个回答

17

从汇编代码中可以看出访问 weakPtr 会生成一个 objc_loadWeak 调用。

根据 Clang 文档objc_loadWeak 会对对象进行 retain 和 autorelease 操作,与以下代码等效:

id objc_loadWeak(id *object) {
  return objc_autorelease(objc_loadWeakRetained(object));
}

这(希望如此)解释了为什么两者都是

if(strongPtr == weakPtr) ...

NSLog(@"weakPtr: %@", weakPtr);

创建额外的自动释放引用。

这不是一个特殊的NSString问题,我可以使用自定义(普通)类复现相同的行为。


1
听起来有点高级,但还是合理的 ;) 谢谢。 - radekEm
1
好发现!我一直在看汇编代码,但没有想到要查找 objc_loadWeak 调用。这是否意味着每个弱引用访问都需要经过两个额外的内存管理调用? - zoul
1
@zoul:谢谢!-是的,即使在优化(发布)代码中,我也发现了这些调用。原因可能是为了避免在多线程环境中访问弱引用时将其置为空,但这只是猜测。-顺便说一句,我通过使用自定义的非ARC类来解决了这个问题,该类覆盖了retain/release/autorelease。(我从不承认我首先在调试器中检查了retainCount,因为那会立即导致负评 :-) - Martin R
感谢您的发现...这让人感到沮丧,因为有这个:http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html在堆栈上使用__weak变量时要小心。请考虑以下示例:NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string); 日志语句显示string具有空值。(编译器在这种情况下提供警告。) - Jon
不确定这是否仍然有效。请查看我的答案,其中包含稍微清理过的原始帖子代码版本。 - Mark A. Donohoe
显示剩余3条评论

3
首先,在NSString上不要尝试使用弱引用或其他内存管理行为,因为这个类中有太多的魔法。不是说弱引用不能与NSString一起使用,只是它的行为比你想象的稍微棘手一些,并且很容易导致不正确的结论。请参阅以下先前的问题: 当您将代码示例包装到自动释放池中并在其后记录弱字符串指针时,它确实是nil。甚至可能出现与NSString之外的其他类类似的行为 - 您只是无法保证弱引用将在您失去对象的最后一个强引用的确切时刻清除。或者也许你是,但很难确定最后一个强引用何时消失,因为autorelease池正在运行,正如该示例所示(并由Martin的答案很好地解释)。

是的,谢谢你...我想你可以将我的帖子标记为可能重复 (我没有足够的声望) https://dev59.com/ImYq5IYBdhLWcg3w4Ehx - radekEm
这不是 NSString 的特殊问题。我已经使用自定义类复现了相同的行为。访问弱指针会创建自动释放的引用,请参考我的答案。 - Martin R
@guitar_freak:保留这个问题是很好的,因为Martin的答案精确地解释了行为。你能把它标记为被接受的吗? - zoul

1

我不确定原帖的问题和/或接受的答案是否仍然有效,至少在我使用iOS9/Xcode7时看到的结果中是无效的。

以下是原帖代码的(稍微清理过的)版本...

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        NSString* __strong strongPtr = [[NSString alloc] initWithFormat:@"Life, Universe, Everything: %d", 42];
        NSString* __weak   weakPtr   = strongPtr;

        NSLog(strongPtr == weakPtr ? @"Same" : @"Different");
        NSLog(@"  StrongPtr: %@", strongPtr);
        NSLog(@"  weakPtr:   %@", weakPtr);

        NSLog(@"Changing strongPtr to something else...");
        // After this is set, there is no strong reference to the created object
        strongPtr = [[NSString alloc] initWithFormat:@"Drink: %@", @"Pan-galactic Gargle Blaster!"];

        NSLog(strongPtr == weakPtr ? @"Same" : @"Different");
        NSLog(@"  StrongPtr: %@", strongPtr);
        NSLog(@"  weakPtr:   %@", weakPtr);
    }

    return 0;
}

这里是(截断的)输出...
Same
  StrongPtr: Life, Universe, Everything: 42
  weakPtr:   Life, Universe, Everything: 42

Changing strongPtr to something else...

Different
  StrongPtr: Drink: Pan-galactic Gargle Blaster!
  weakPtr:   (null)

Program ended with exit code: 0

根据被接受答案的解释,访问条件语句中的弱引用并没有保留自动释放的引用,可以通过输出中的(null)看到。

......还是我意外改变了OP的问题,以至于隐藏了他所看到的东西?或者现在默认开启了ARC?


1

当你执行以下代码:

strongPtr = [[NSString alloc] initWithString:@"abc"]

你的 strongPtr 指向一个新分配的对象,由于它之前指向的对象没有被释放,因此弱指针仍然指向一个有效的地址。

顺便说一下,你可以使用以下代码打印对象的内存地址:

NSLog(@"%@", [NSString stringWithFormat:@"%p", theObject])


是的,但如果我写 strongPtr = nil,结果是一样的。 - radekEm
假设使用ARC,之前的实例不可能泄漏并且应该被释放。最终会被释放。 - zoul
strongPtr = nil并不等同于向一个对象发送dealloc。 - peko
2
@peko,但它应该将所有弱引用清零,假设没有其他强引用。 - James Webster
我认为问题在于仍然存在一个引用...这是一个自动释放的引用,它将在退出当前自动释放池时被清除。试一下。将上述内容包装在AutoRelease池中,然后在该池之外检查该值。相信此时它将为空,正如预期的那样。此外,请参考我的示例,以获取另一种视角。 - Mark A. Donohoe

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