Why isn’t my weak reference cleared right after the strong ones are gone?
Asked Answered
A

4

28

I am a little bit stubborn, but I want to understand weak and strong references well, so that's why I'm asking you once again.

Consider this:

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

The result is myText: (null) and it is pretty obvious - weak reference is set to null just after assignment, cause there is no strong reference to the pointed object.

But in this case:

__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);

The output:

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   // <== ??

My question:

Why after strongPtr = [[NSString alloc] initWithString:@"abc"]; weak pointer value is not changed to nil (why the object created at the beginning still exists in memory, despite it does not have any strong refs? -- or maybe it has?)


I have tried that one: (but it is not good for adding a comment I suppose). I have included the code where I am creating a strongPtr in @autorealesepool. I not sure if it is correct solution but it work...

 __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);

Output:

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)
Able answered 7/3, 2013 at 8:28 Comment(4)
+1 Very good example of a question!Meister
@FruityGeek, could you please explain the problem instead of just dropping smart remarks?Sacramentalism
@FruityGeek: Any other hints?Able
Both if(strongPtr == weakPtr) and NSLog(@"weakPtr: %@", weakPtr); seem to create temporary/autoreleased references. If you remove these lines, weakPtr is nil at the end as expecte.Idona
I
17

From the assembly code it can be seen that accessing weakPtr generates a objc_loadWeak call.

According to the Clang documentation, objc_loadWeak retains and autoreleases the object and is equivalent to

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

This (hopefully) explains why both

if(strongPtr == weakPtr) ...

and

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

create additional autoreleased references.

This is not a special NSString problem, I could reproduce the same behaviour with a custom (plain) class.

Idona answered 7/3, 2013 at 9:21 Comment(8)
Sounds like a little bit advanced stuff, but reasonable ;) Thanks.Able
Nice catch! I was squinting at the assembly for a while, but didn’t get as far as to look up the objc_loadWeak call. Does that really mean that each weak reference access goes through two extra memory management calls?Sacramentalism
@zoul: Thank you! - Yes it seems so, even in optimized (release) code I found the calls. The reason could be to avoid that the weak reference is nilled while being accessed in a multithreading environment, but that is pure guessing. - Btw. I figured this out by using a custom non-ARC class that overrides retain/release/autorelease. (I would never admit that I checked the retainCount in the debugger first, because that causes immediate downvotes :-)Idona
It’s a nice guarantee to know that weak references can’t just disappear in the middle of your code block, otherwise a plain if (weakRef != nil) {…} would be pointless. (Similar thing is guaranteed by ARC for method arguments.) As for the retainCount lapse, I can’t downvote you now, but half an hour of self-flaggelation will do.Sacramentalism
thanks for this find... frustrating because of this: developer.apple.com/library/mac/#releasenotes/ObjectiveC/… Take care when using __weak variables on the stack. Consider the following example: NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string); The log statement shows that string has a null value. (The compiler provides a warning in this situation.)Possible
@MartinR The documentation explicitly mentions the rationale: "Loading weak references would be inherently prone to race conditions without the retain."Herndon
@Sacramentalism The documentation says that objc_loadWeak is equivalent to the two method calls, I'm guessing the actual code is inlined or optimized in assembly.Herndon
Not sure if this is still valid. Check out my answer which has a slightly cleaned-up version of the OPs code.Roorback
S
3

First, don’t experiment with weak references or other memory management behaviour on NSString, there’s too much magic in that class. Not that weak references wouldn’t work with NSString, just the behaviour is slightly trickier than you would expect and easily leads to incorrect conclusions. See these previous questions:

When you wrap your code example with an autorelease pool and log the weak string pointer afterwards, it’s nil indeed. It might even be the case that you would get similar behaviour with classes other than NSString – you are simply not guaranteed that the weak references will be cleared at the precise moment you lose the last strong reference to an object. Or maybe you are, but it’s hard to tell when exactly the last strong reference disappears because of the autorelease pools in play, as hinted to by this example (and nicely explained by Martin’s answer).

Sacramentalism answered 7/3, 2013 at 8:46 Comment(3)
Yeah, thanks for that... I think you can mark my post as possible duplicate (I do not have enough reputation) of #14117493Able
This is not a special issue of NSString. I have reproduced the same behaviour with a custom class. Accessing the weak pointer creates autoreleased references, see my answer.Idona
@guitar_freak: It’s good to keep this question around, since Martin’s answer precisely explains the behaviour. Can you please mark it as the accepted one?Sacramentalism
P
1

when you do

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

you strongPtr is pointing to new allocated object, and since the previous object it was pointing too didn't get deallocated, the weak pointer still points to a valid address.

btw. you can print the memory address off an object with

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

Petrochemical answered 7/3, 2013 at 8:46 Comment(5)
Yeah, but if I write strongPtr = nil, the result is the same.Able
Assuming ARC, the previous instance could not have leaked and should be deallocated. Eventually.Sacramentalism
strongPtr = nil is not the same as sending dealloc to an object.Petrochemical
@peko, but it should zero any weak references assuming there are no other strong referencesMeister
I believe the issue is there is a reference still being held... an auto-released reference that will get cleaned up as soon as you exit the current autorelease pool. Try it. Wrap the above in an AutoRelease pool, then check the value outside of that pool. Pretty sure it will be null at that point as expected. Also, see my example for another perspective on this.Roorback
R
1

Not sure the OP's question and/or the accepted answer here is still valid, at least not as of the results I'm seeing with iOS9/Xcode7.

Here's a (slightly cleaned up) version of the OP's code...

#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;
}

And here's the (truncated) output...

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

Here accessing the weak references in the conditionals (as per the accepted answer's explanation) doesn't keep an auto-released reference around as you can see by the (null) in the output.

...or did I accidentally change the OP's question to the point where I hid what he's seeing? Or perhaps it's because now ARC is on by default?

Roorback answered 1/6, 2016 at 20:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.