iOS10 __weak pointer made retainCount +1

223 views Asked by At

When I use __weak pointer refer to an NSObject, the unexpected retainCount is shown.

The test code and result are as the image below. Result

Here is the code:

    id obj1 = [[NSObject alloc] init];
    id __weak obj2 = obj1;
    NSLog(@"obj1: %ld", CFGetRetainCount((__bridge CFTypeRef)obj1));        // line 31
    NSLog(@"obj2: %ld", CFGetRetainCount((__bridge CFTypeRef)obj2));        // line 32
    NSLog(@"obj1 again: %ld", CFGetRetainCount((__bridge CFTypeRef)obj1));  // line 33

So~ My confusion is that, obj2's retainCount is expected to 1, Why retainCount is 2?

I've read from book: the __weak pointer registered the object to the autoreleasepool, so the retain count + 1.

However, obj1 and obj2 refer to the same memory address, in that case, the obj1's retainCount should also become 2. But, it still remain in 1.

I know retainCount is unreliable but I'm so curious about how this came. (My environment is Xcode 8.3.3, iOS 10.3)

Greatly appreciate if anyone can explain this to a beginner :)

1

There are 1 answers

1
rob mayoff On

The exact value of the retain count is generally an implementation detail that depends on the compiler, the Objective-C language runtime, and any other libraries involved (like Foundation). But it's reasonable to wonder why it behaves this way, as long as you don't depend on the behavior.

Either your book is wrong, or you misunderstood it. Using __weak doesn't put the object in the autoreleasepool.

Here is what is happening on line 32 (the one with the @"obj2: %ld" format string).

To safely pass an object reference to a function or method (like NSLog), it has to be a strongly-held reference. So the compiler generates a call to objc_loadWeakRetained on obj2. This function atomically increments the retain count of the object and returns the reference. Thus the retain count of the object goes from 1 to 2 before the object reference is passed to NSLog.

Then, after NSLog returns, the compiler generates a call to objc_release. Thus the retain count goes back down from 2 to 1 by the third NSLog.

If you want to be sure what's happening, look at the assembler output of the compiler. You can ask Xcode to show it to you:

assembly from menu

Here's the relevant part of the assembly:

    .loc    2 0 9 discriminator 1   ## /Users/mayoff/TestProjects/test/test/main.m:0:9
    leaq    -32(%rbp), %rdi
    ##DEBUG_VALUE: obj2 <- [%RBP+-32]
    .loc    2 16 66 is_stmt 1       ## /Users/mayoff/TestProjects/test/test/main.m:16:66
    callq   _objc_loadWeakRetained
    movq    %rax, %rbx
Ltmp4:
    .loc    2 16 29 is_stmt 0       ## /Users/mayoff/TestProjects/test/test/main.m:16:29
    movq    %rbx, %rdi
    callq   _CFGetRetainCount
    movq    %rax, %rcx
Ltmp5:
Ltmp20:
## BB#3:
    ##DEBUG_VALUE: obj2 <- [%RBP+-32]
    ##DEBUG_VALUE: obj1 <- %R15
Ltmp6:
    .loc    2 16 9 discriminator 1  ## /Users/mayoff/TestProjects/test/test/main.m:16:9
    leaq    L__unnamed_cfstring_.4(%rip), %rdi
    xorl    %eax, %eax
    movq    %rcx, %rsi
    callq   _NSLog
Ltmp7:
Ltmp21:
## BB#4:
    ##DEBUG_VALUE: obj2 <- [%RBP+-32]
    ##DEBUG_VALUE: obj1 <- %R15
    .loc    2 16 9 discriminator 2  ## /Users/mayoff/TestProjects/test/test/main.m:16:9
    movq    %rbx, %rdi
    callq   *_objc_release@GOTPCREL(%rip)

Line 16 is (in my test) the one that uses obj2. You can see the call to objc_loadWeakRetained before the call to CFGetRetainCount, and the call to objc_release after NSLog returns.