I’m Charles Nyisztor, and I welcome you to this new episode on Swift.
In this video, I am going to talk about strong reference cycles that can occur in Swift when you assign a closure to a class property, and the closure captures the instance.
All right, let’s switch to XCode to illustrate the problem.
I am going to create a very simple Contact class. It has two properties of type String: name and phoneNumber. Note that phoneNumber is optional.
Let’s implement the initializer next.
When a new Contact instance is created, a dedicated console message is displayed with the contact’s name.
The johnDoe instance is optional, so we can set it to nil. And when we do so, the memory allocated for this instance should be automatically released.
To prove this, I implement the deinit() method. It simply displays a console message stating that the given instance is gone.
Indeed, the instance is deallocated as we assumed.
Now, let’s implement a lazy property called prettyPrint. I assign a closure to this property; the closure returns a formatted string which contains the contact’s name and also the phoneNumber if it’s been set.
Next, let’s add a phone number to our John Doe contact, and display the prettyPrint property to the console. Although the name and the phone number appear nicely in the console, something important is missing: there is no indication that the deinit() method was called. This means that our johnDoe instance has not been deallocated, which is definitely bad.
Closures are reference types, and when we assign a closure to a property, it will create a strong reference to the given closure. On the other hand, the closure will capture the instance if we access a property or call a method of the instance from the closure’s body. In the end we’ll have two strong references that will keep each other alive.
To solve the problem, we must break the strong reference cycle between the instance and the closure. Swift provides the means for that by defining a capture list. An item in a capture list can be a reference to a class instance such as self, paired with the unowned or weak keyword. We can also include variables initialized with a value in the capture list and mark them as weak or unowned.
Back to our example, let’s create the capture list. I mark the self reference as unowned, which indicates that the captured instance is guaranteed to be valid in the closure’s body. You should not use unowned references for long-running, asynchronous operations since the captured items may become nil. If an unowned reference becomes nil, executing the closure which captures it will instantly crash the app.
In our example, we know that self won’t become invalid, therefore we can use it safely.
If the reference or the variable used within the closure’s body can become nil, we should mark it as weak. When using weak we must check whether the instance is still valid.
If I change the self reference from unowned to weak, I have to also refactor the closure’s body to implement validity checks for the weak reference.
We could break the strong reference cycles with closures by using closure capture lists. We can mark references to class instances or variables that are used within the closure body as unowned or weak.
As a rule of thumb: use unowned if you are sure that the captured reference will never become nil; if you are unsure about this, rather use weak and implement the required validity checks in the closure’s body.
Have you enjoyed this tutorial? Subscribe to my channel for further, Swift related videos.
I plan to add new videos on a regular basis. Also, feel free to visit my website and check out my apps, my online courses, and other cool stuff.