2

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?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
kellanburket
  • 12,250
  • 3
  • 46
  • 73

1 Answers1

8

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.

Max Komarychev
  • 2,836
  • 1
  • 21
  • 32
  • `aClosure` was syntactically incorrect. I changed it above--now this would, according to [apple's documentation](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID48) lead to a strong reference cycle, right? – kellanburket Mar 04 '15 at 20:47
  • 1
    You're right: first version without `lazy` was incorrect. And no: your example does not produce reference cycle since you capture `self` with `unowned` keyword. The difference between `weak` and `unowned` is that `weak` will chanage type to optional while `unowned` - not. But both of them produce a weak reference. Please check out this answer http://stackoverflow.com/a/26025176 for differences between `weak` and `unowned` – Max Komarychev Mar 04 '15 at 20:53
  • no, that's what I meant originally, but then reversed it in the comment--it won't declare a strong reference cycle because of the capture list, but it would without. Sorry for the confusion and thanks for your perspective on this! – kellanburket Mar 04 '15 at 20:55
  • "it will be kept alive because of captured self even though it will not be displayed on the screen." That's not necessarily a problem – newacct Jul 24 '15 at 21:30