1

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 :)

rob mayoff
  • 342,380
  • 53
  • 730
  • 766
Rango
  • 21
  • 5
  • Because of an implementation detail. Try turning on optimization. Or targeting a different platform. `retainCount` isn't just unreliable, it is useless. In fact, the output is even more confusing than you allude; `obj1` and `obj2` are identical. This is likely related to making a function call w/`obj2`. You could always disassemble the code and see. – bbum Dec 05 '17 at 15:35
  • As the documentation states: don't use `- retainCount` or `CFRetainCount`. The values returned are not reliable. See also [here](https://stackoverflow.com/questions/4636146/when-to-use-retaincount) or [here](https://stackoverflow.com/questions/35172268/why-retain-count-is-diffrent-in-debug-mode-and-in-running-mode). – mschmidt Dec 05 '17 at 16:00
  • @mschmidt Thanks! Learnt a lot in that question. – Rango Dec 05 '17 at 16:21
  • 1
    @bbum retainCount is more unreliable than I expected, thanks anyway! – Rango Dec 05 '17 at 16:39
  • This question is not a duplicate of [`When to use -retainCount`](https://stackoverflow.com/questions/4636146/when-to-use-retaincount). This question is about implementation details, and it is reasonable to ask about implementation details. – rob mayoff Dec 05 '17 at 17:08

1 Answers1

7

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.

rob mayoff
  • 342,380
  • 53
  • 730
  • 766
  • Thanks, Rob. Note that the generated assembly, including the retains/releases, are going to change across compilers, releases, and architectures. – bbum Dec 05 '17 at 21:57