Capturing Reference Types

Scott Gardner
asyncio (as.ync.io)
7 min readMar 29, 2016

This being a blog about reactive programming with RxSwift (and friends) — which rely heavily on asynchronicity and closures — I’d like to kick things off by covering one of the most commonly misunderstood things about writing asynchronous code in Swift: how to properly work with reference types in closures.

Fortunately, the rules of engagement are pretty straightforward. Follow them and you should be fine. Ignore them and, well, bad things can happen.

I’ll start with a simple example.

You can clone the demo project from the GitHub project badge at the top and follow along by checking out the specified branch. You can check out a branch in Xcode by selecting Source Control > CapturingReferenceTypes > Switch Branch… from the menu. Because Interface Builder files may automatically be modified by Xcode simply by selecting them, you may need to select a non-Interface Builder file before switching to a different branch, and then discard (or commit) changes, which you can also do from the Source Control menu item.

01_Simple_Example_1
import UIKit

class ViewController: UIViewController {

var random: CGFloat {
return CGFloat(drand48())
}

override func viewDidLoad() {
super.viewDidLoad()
srand48(time(nil))
}

@IBAction func handleRefreshButtonTapped(_: AnyObject) {

let backgroundColor = UIColor(red: random, green: random, blue: random, alpha: 1.0)

UIView.animateWithDuration(0.3) {
self.view.backgroundColor = backgroundColor
}

}

}

In handleRefreshButtonTapped(_:), I create a random color and then assign it to the view’s backgroundColor in UIView.animateWithDuration(_:animations:)’s animations closure. In the app, each time the refresh button is tapped, the view’s background color will change via a short animation:

01_Simple_Example_2

By accessing self in the animations closure, I am capturing self (aka closing over self). Notice that I’ve explicitly written self in the closure. This is required by the compiler, to make sure I’m aware that self is being captured in the closure. If I omitted it, I’d get an error:

01_Simple_Example_3

When a closure captures a reference type, by default it creates a strong reference to that reference type. And if that captured reference type also has a strong reference to the closure, a strong reference cycle is created between the two reference types, and neither can be deallocated. The result is a memory leak.

Capturing self is harmless in this simple example, because the view controller does not ever get deallocated (it’s a single-view app). But in a more complex app, this could cause a memory leak or even a crash. To demonstrate how that could happen, I’ll modify the example project.

Summary of changes:

  1. Installed CocoaPods, a fine dependency manager.
  2. Installed Async, a lightweight wrapper around GCD.
  3. Installed RxSwift and RxCocoa, the official reactive extensions for Swift and Cocoa.
  4. Rx-ified the project. More on this in a moment.
  5. The background-color-changing view controller is now being presented in a popover.
  6. Added a 5-second delay before changing the background color after tapping the refresh button.
02_Rx-ify_Simple_Example_1
import UIKit
import Async
import RxSwift
import RxCocoa

class ViewController: UIViewController {

@IBOutlet weak var refreshButton: UIBarButtonItem!

var random: CGFloat {
return CGFloat(drand48())
}

let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
srand48(time(nil))

refreshButton.rx_tap
.bindNext {

let backgroundColor = UIColor(red: self.random, green: self.random, blue: self.random, alpha: 1.0)

Async.main(after: 5.0) {

UIView.animateWithDuration(0.3) {
self.view.backgroundColor = backgroundColor
print("animateWithDuration(_:animations:) was executed")
}

}
}
.addDisposableTo(disposeBag)
}

deinit {
print("ViewController will be deallocated")
}

}

I’m going to go over the ins and outs of RxSwift, RxCocoa, and additional Rx libraries in future posts. If you’re new to Rx, though, I highly encourage you to head over to RxSwift’s home page and read the Getting Started guide, download the repo, and review the example code in the Rx.playground and RxExample project. These are excellent resources!

In viewDidLoad, I am now binding tap events on the refreshButton to a closure that does the same thing as was previously being done in handleRefreshButtonTapped(_:), except now the animation block is nested in an Async block that delays its execution by 5 seconds. I’ve also added a couple print statements so that I can see when the animations closure is executed, and when ViewController is about to be deallocated.

Notice that I am capturing self in the closure parameter to bindNext(_:) (in addition to the animations closure, as before).

In the app, I will first tap the refresh button and wait for the animation to complete before tapping Done to dismiss ViewController, and then I’ll tap the refresh button and Done immediately after, without waiting for the animation to complete. In both cases, ViewController never gets deallocated, evidenced by the fact that the print statement in deint never executes:

02_Rx-ify_Simple_Example_2

The reason ViewController never gets deallocated is because it has a strong reference to the closure parameter of bindNext(_:), and that closure captures and holds a strong reference to the ViewController. It’s deadlock, and that memory is leaked. If there were enough of these strong reference cycles occurring in an app, the memory pressure would eventually cause the app to be killed.

What I need to do is define a capture list. A capture list defines rules for how to capture one or more reference types in the closure, as follows:

If it is possible that the capture can become nil (such as by being deallocated) before closure is executed, define the capture as weak. If not, that is, the capture and the closure will always be deallocated at the same time, define the capture as unowned.

So now I have to decide whether to define a weak or unowned capture of self.

Both weak and unowned captures will not prevent ARC from disposing of the capture if they are the last reference to that capture. The difference between the two is that weak can be set to nil (and, thus, must be an optional), and unowned cannot.

The syntax to define a capture list is to enclose a rule (or multiple rules in a comma-separated list) inside square brackets, within the closure body, after the opening curly brace and before the closure’s parameter list and return value (if provided, and cannot be inferred), followed by the in keyword. A rule consists of the weak or unowned keyword followed by a single capture.

refreshButton.rx_tap
.bindNext { [unowned self] in

let backgroundColor = UIColor(red: self.random, green: self.random, blue: self.random, alpha: 1.0)

Async.main(after: 5.0) {

UIView.animateWithDuration(0.3) {
self.view.backgroundColor = backgroundColor
print("animateWithDuration(_:animations:) was executed")
}

}
}
.addDisposableTo(disposeBag)

I have modified the bindNext(_:) call to define an unowned capture of self. In the app, if I tap the refresh button and wait for the animation to complete before tapping Done, ViewController is properly deallocated, the capture of self is released, and all is good. But, what happens if I do not wait for the animation to complete, and instead tap the refresh button immediately followed by Done? ViewController is deallocated (self becomes nil), but the capture of self in the closure cannot be set to nil, because we defined it as unowned, which is non-optional. The closure will not be released until it is executed. So, when the closure is executed and attempts to access its self capture, an EXC_BAD_ACCESS exception is thrown (technically, swift_unknownUnownedTakeStrong()):

Clearly, unowned is not the right choice here, because the capture can become nil before the closure is executed. I must use a weak capture.

refreshButton.rx_tap
.bindNext { [weak self] in
guard let `self` = self else { return }

let backgroundColor = UIColor(red: self.random, green: self.random, blue: self.random, alpha: 1.0)

Async.main(after: 5.0) {

UIView.animateWithDuration(0.3) {
self.view.backgroundColor = backgroundColor
print("animateWithDuration(_:animations:) was executed")
}

}
}
.addDisposableTo(disposeBag)

I have changed the capture to weak, which makes it an optional. Then, at the top, I used a guard statement to unwrap the capture and assign it to a local strongSelf constant if it is not nil, or else simply return if it is nil. Thanks to Marin Todorov for sharing the ‘self’ idea (source)! That way, I am able to still reference self within the scope of bindNext(_:), but self is now referring to the local capture. And now all is good:

I hope you enjoyed this article. Nothing says “thanks” like a share. Cheers!

Originally published at as.ync.io.

--

--

Scott Gardner
asyncio (as.ync.io)

Swift & iOS app developer, instructor, author & speaker