Search code examples
objective-ccompletionhandlerretain-cycle

Local Object became nil before returning completion block - Objective C


I have an object that declared inside a function. this object makes a function call which has a completion block. the function executes properly.

This function makes a network call (which is in another class). after getting the result from network call, I am making a check to see if the class is still in memory (using weakSelf and strongSelf)

during this check, it shows, the self is nil.

I know if I use a class method or property variable I can resolve this. but is there any way to retain this object (which declared inside a function). I tried __strong and __block with the object, but not working.

Here is my code

@implementation AViewController {
        -(void)viewWillAppear:(BOOL)animated {
            [super viewWillAppear:animated];
            [self methodA];
        }
        -(void)methodA {
            LocalClass *object = [LocalClass alloc] init];
            [object aMethodWithCompletionBlock:^(NSDictionay *result) {

            }];

        }
    }
    @implementation LocalClass {
        - (void)aMethodWithCompletionBlock:(void (^)(NSDictionay *result))completionHandler {

            __weak typeof(self) weakSelf = self;
            [NetworkClass methodToMakeRESTRequestOnComplete:^(NSDictionay *someResult) {
                __strong typeof(self) strongSelf = weakSelf;
            if(!strongSelf) { // this check fails as strongSelf is nil
                return;
            }

            //some code execution
            if (completionHandler != nil) {
                completionHandler(someModifiedResult);
            }
            }];
        }
    }

Solution

  • I know if I use a class method or property variable I can resolve this. but is there any way to retain this object (which declared inside a function). I tried __strong and __block with the object, but not working.

    Your code is trying to solve a problem which doesn't exist and it doing so creates one.

    The purpose of the weakSelf/strongSelf pattern it to deal with harmful reference cycles (not all reference cycles are harmful, indeed some are useful). You should only use it if you have identified you that there is such a harmful cycle.

    Let's look at your code without any weakSelf/strongSelf dance:

     1 @implementation AViewController
     2 {
     3    -(void)methodA
     4    {  LocalClass *object = [LocalClass alloc] init];
     5       [object aMethodWithCompletionBlock:^(NSDictionay *result) { ... }];
     6    }
     7 }
     8 
     9 @implementation LocalClass
    10 {
    11    - (void)aMethodWithCompletionBlock:(void (^)(NSDictionay *result))completionHandler
    12    {  [NetworkClass methodToMakeRESTRequestOnComplete:^(NSDictionay *someResult)
    13       {
    14          if (!self)
    15             return;
    16 
    17          //some code execution
    18          if (completionHandler != nil)
    19             completionHandler(someModifiedResult);
    20       }];
    21    }
    22 }
    

    Now what will happen when you have an instance of AViewController and invoke methodA?

    1. At line 3 a new instance of LocalClass is created and a reference to it stored in object. The type of object is implicitly __strong LocalClass * and so the new instance has a strong reference to it and will stay alive.
    2. At line 5 the method aMethodWithCompletionBlock: is called on the object referenced by object passing it a closure. Note that neither methodA or the instance of AViewController it is being called for keep a reference to this closure, it is just passed to the method. Therefore after the call there is no possibility of a reference cycle between a local variable belonging to methodA or an instance variable belonging to AViewController and the closure.
    3. At line 12 method methodToMakeRESTRequestOnComplete: of NetworkClass is called passing it a closure

      • This closure references self so it contains a strong reference back to the instance of LocalClass on which aMethodWithCompletionBlock: was called
      • This is the same instance of LocalClass that was created at Line 3 and so there are now two strong references to that object.
      • The closure also contains a strong reference to the block referenced by the parameter completionHandler
    4. At line 20 methodToMakeRESTRequestOnComplete: returns, as the passed block is a completion block it is unlikely to have been called yet. So at this point NetworkClass has a reference to that completion block, and the completion block has a reference to the LocalClass instance.

    5. At line 21 aMethodWithCompletionBlock: returns. The instance of LocalClass it was called on has kept no references to the parameter completionHandler.
    6. At line 6 methodA returns. This destroys its local variable object which drops the strong reference to the LocalClass instance it referenced. The system can consider destroying that instance at this point, however as NetworkClass has a strong reference to a completion block which in turn has a strong reference to this same LocalClass instance it is still needed and not destroyed.
    7. At some future time line 14 is reached after NetworkClass invokes the block reference it kept. The self variable contains a strong reference to the LocalClass instance originally created at line 4, that instance therefore still exists and all is well with the world.
    8. At line 20 the completion block returns. If at this point NetworkClass drops its strong reference to the block the block can be (probably – assuming there are no other strong references to it) destroyed. That destruction removes the strong references the block has to the objects referenced by its self and completionHandler and so those objects can also (probably...) be destroyed and the objected originally created at line 4 bites the dust.

    There are no harmful cycles, no need for any weak references to manage them.

    HTH