Search code examples
iosswiftmemory-leaksclosuresstrong-references

Strong reference cycles with closures


I'm trying to understand when I need to be on the look out for possible memory leaks caused by strong reference cycles. From what I've been able to glean from the swift documentation, using a self reference in a closure declared as an instance property within the same instance will cause a strong reference cycle unless I declare a capture list, e.g.:

class A {

    var a: String

    lazy var aClosure: () -> () = { [unowned self] in
        println(self.a)
    }

    init(a: String) {
        self.a = a
    }
}

Now what happens with closures that aren't stored as instance properties or closures that are stored as instance properties of other classes? Do I still need to be worried about strong reference cycles in these cases?


Solution

  • The case you asking about does not lead to reference cycle.

    Reference cycle is only occurs when when 2 or more objects directly or indirectly have pointer (or captured by a block inside a property) to each other :

    A->B and B->A (direct)
    A->B, B->C, C->A (indirect)
    

    Now what happens with closures that aren't stored as instance properties

    Often you may see a view controller that calls some library and provide the handler block. For example:

    // inside some method of view controller
    APIWrapper.sharedInstance().callApiWithHandler(handler: { 
         (finished: Bool) -> Void in
        // process result of the action
        self.showResults()
    }
    

    In this case you don't know how long will take this action to complete. Your block may be submitted into private operation queue. All captured object will be kept alive until this action finishes.

    Now the danger part even: if user presses back button (assume the navigation controller is involved) and current view controllers is popped out of navigation stack it will be kept alive because of captured self even though it will not be displayed on the screen.

    This should be rewritten as:

        // inside some method of view controller
        APIWrapper.sharedInstance().callApiWithHandler(handler: { 
             [weak self]
             (finished: Bool) -> Void in
            // process result of the action
            self?.showResults()
        }
    

    closures that are stored as instance properties of other classes

    Similar for this part: you may not be able to control lifecycle of the object that keeps reference to the block.

    Object captured by is a implicit reference and may be hard to debug.

    To sum up: working with blocks you should always think of how long will this block live and whether it produces cycle or not. It is a good practice to capture objects with weak/unowned unless you have good reasons not to do so.