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)
René Hoffmann
  • 2,766
  • 2
  • 20
  • 43
radekEm
  • 4,617
  • 6
  • 32
  • 45

4 Answers4

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.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Sounds like a little bit advanced stuff, but reasonable ;) Thanks. – radekEm Mar 07 '13 at 09:26
  • 1
    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? – zoul Mar 07 '13 at 09:36
  • 1
    @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 :-) – Martin R Mar 07 '13 at 09:45
  • 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](http://stackoverflow.com/questions/10310441/why-does-arc-retain-method-arguments) 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. – zoul Mar 07 '13 at 09:52
  • thanks for this find... frustrating because of this: http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html 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.) – Jon Jul 25 '13 at 18:42
  • @MartinR The documentation explicitly mentions the rationale: "Loading weak references would be inherently prone to race conditions without the retain." – Patrick Pijnappel Apr 22 '14 at 02:56
  • @zoul 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. – Patrick Pijnappel Apr 22 '14 at 02:59
  • Not sure if this is still valid. Check out my answer which has a slightly cleaned-up version of the OPs code. – Mark A. Donohoe Jun 01 '16 at 20:09
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).

Community
  • 1
  • 1
zoul
  • 102,279
  • 44
  • 260
  • 354
  • Yeah, thanks for that... I think you can mark my post as possible duplicate (I do not have enough reputation) of http://stackoverflow.com/questions/14116993/objective-c-weak-attritube-dont-work-as-expected – radekEm Mar 07 '13 at 09:18
  • 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. – Martin R Mar 07 '13 at 09:24
  • @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? – zoul Mar 07 '13 at 09:31
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])

peko
  • 11,267
  • 4
  • 33
  • 48
  • Yeah, but if I write strongPtr = nil, the result is the same. – radekEm Mar 07 '13 at 08:50
  • Assuming ARC, the previous instance could not have leaked and should be deallocated. Eventually. – zoul Mar 07 '13 at 08:50
  • strongPtr = nil is not the same as sending dealloc to an object. – peko Mar 07 '13 at 08:51
  • 2
    @peko, but it should zero any weak references assuming there are no other strong references – James Webster Mar 07 '13 at 08:56
  • 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. – Mark A. Donohoe Feb 24 '20 at 00:19
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?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286