-1

The following function will not print "nil1" or "nil2" if I call it once. However, if I put it in a loop, some iterations will print "nil2" ONLY.

What's going on? If user is nil, why "nil1" is not printed?

After remove the __weak directive before the declaration of "weakUser", the function will no longer print "nil" even though it is called multiple times in a loop.

Any ideas of what's going on?

- (void)hello: (void(^))callback
{
    User *user = [[User alloc] init]; // user is not nil 

    __weak User *weakUser = user; // removing __weak won't cause problems.
    if(weakUser == nil)
    {
        NSLog(@"nil1");
    }
    [ABC func: ^{
        if(weakUser == nil)
        {
            NSLog(@"nil2");
        }
        callback();
    }];
}
user2127480
  • 4,623
  • 5
  • 19
  • 17
  • why the `"nil1"` should be printed? the `weakUser` is not `nil` at all, you just instantiated the object with a strong pointer _in the same scope_ a line above which the `user` points... the `"nil1"` will be never printed in this scenario. – holex Jul 22 '14 at 09:00
  • 1
    the `"nil2"` is printed only if the `user` is released _before_ your block runs. you can count on the `user` will be `nil` immediately when your method is finished, but there is no info about _when_ the block runs exactly in this context, aka _after_ your method is done or _before_. – holex Jul 22 '14 at 09:03
  • if you remove `__weak` from `weakUser`, that pointer will be `__strong` automatically and your block keeps the object alive via `weakUser` until the block is released. – holex Jul 22 '14 at 09:06
  • @holex good point on the block may execute after user gets deallocated when the "hello" call runs out of scope. Thanks. – user2127480 Jul 22 '14 at 17:12

1 Answers1

1

that is a scheduling issue here, so let us just examine your code from perspective of time. I've added some timestamp marker to your code and let us walk through what really happens at those checkpoints.

- (void)hello: (void(^))callback {
    // TIMESTAMP(A)
    User *user = [[User alloc] init]; // user is NOT nil 

    // TIMESTAMP(B)
    __weak User *weakUser = user;

    // TIMESTAMP(C)
    if(weakUser == nil) {
        NSLog(@"nil1");
    }
   
    [ABC func: ^{
        // TIMESTAMP(D)
        if(weakUser == nil) {
            NSLog(@"nil2");
        }
        callback();
    }];

// TIMESTAMP(E)
}

we can agree in (A) < (B) < (C) < (E) and there are two more scenarios here

  1. (C) < (D) < (E) or
  2. (C) < (E) < (D)

there is no more information about which one of the two scenarios happens with (D).


(A)

you instantiate a __strong User * user in this scope, the end of the scope is at (E) therefore your __strong pointer will keep the instance alive in this context to the end of the scope. you have one retaining reference to your object.

(B)

you make a __weak User * weakUser pointer, that points to a __strong reference what you just established recently at (A). the __weak pointers point to an instance as long as it is kept alive by a __strong reference at least, but they are not retaining references – they become nil immediately when no more __strong reference points to the instance.

(C)

you still have a valid __strong reference to your user and the __weak reference point to the same instance shamelessly, so the instance is still alive and the weakUser is not a nil, therefore the true-branch of if will never run – nothing will be printed ever.

(E)

the scope runs out and the __strong reference to your instance won't be live anymore, therefore it does not keep the user alive after that point. additionally the weakUser immediately becomes nil, because that is not a retaining reference and mo more __strong pointer keeps the user alive.


scenarios for (D):

1. (C) < (D) < (E)

the block runs before the time (E) is reached, the __strong pointer still keeps the instance alive and the weakUser still points to the same instance, therefore the true-branch of if won't perform.

2. (C) < (E) < (D)

the block runs after the time (E) is reached, the __strong pointer is not live anymore, so the instance has been released by now and the weakUser is nil, therefore the true-branch of if performs in this scenario.


NOTE: you can read more about encapsulation and ownership on the Apple's official site.

Community
  • 1
  • 1
holex
  • 23,961
  • 7
  • 62
  • 76