One of the confusing aspects of Swift is how capture semantics work with closures. When used improperly, they can result in retain cycles or crash the app with the dreaded
EXC_BAD_ACCESS. It is worth keeping in mind that capture semantics only apply to reference-based objects (classes); value objects can be used freely without worrying about this. The situation I’ll be looking at here is the capture of self, which is far-and-away the most common situation.
Many asynchronous processes such as API calls include a completion closure, and the most common way to provide it is as an inline closure. In this example, we’re storing the API request object that is created by the call to our API client so that we can allow the user to cancel the request if it’s taking too long. Let’s assume this code appears in our LoginViewController class.
[gist https://gist.github.com/JoshuaSullivan/34cdb4315d79b0a1d85d file=”ForgotCaptureSemantics.swift”]
Whoops! The compiler is mad at us because we have calls to other methods in the class which have an implicit
self in front of them. Swift requires that you be explicit about capturing references to objects to avoid unexpected behavior. Fine, let’s add
[gist https://gist.github.com/JoshuaSullivan/34cdb4315d79b0a1d85d file=”StrongCaptureSemantics.swift”]
Better! However, the closure is now holding a strong reference to
self and the LoginViewController is holding a strong reference to the closure. This creates a retain cycle and will cause the LoginViewController to be kept alive, even if the user navigates away from this screen. Even if the login was successful and the completion closure was invoked, it still exists and maintains its capture of
self unless you explicitly nil the reference as part of the completion. We don’t want to go leaking view controllers all willy-nilly, so let’s try that
[unowned self] thing we saw in some WWDC video:
[gist https://gist.github.com/JoshuaSullivan/34cdb4315d79b0a1d85d file=”UnownedCaptureSemantics.swift”]
Right, now we definitely don’t have a strong reference to
self! However, users are reporting the app is crashing when login is taking too long and they leave the screen before it completes. Looking at the crash logs, you see a rash of
EXC_BAD_ACCESS events occurring. Because you declared
self was unowned, it was deallocated when the users left the screen, but when the API call completed, it attempted to call the methods referenced in the closure to disastrous effect.
unowned as a capture semantic is the equivalent of force-unwrapping an optional. It’s never a great idea and should only be done when you are 100% sure there’s no chance the captured object will be deallocated before it is invoked. When you’re dealing with long-running asynchronous tasks like API requests, this is a bad bet unless the originating class is a singleton or some other pattern which will guarantee the object exists for the lifetime of the app.
For all other cases, consider this pattern using
weak capture semantics:
[gist https://gist.github.com/JoshuaSullivan/34cdb4315d79b0a1d85d file=”WeakCaptureSemantics.swift”]
The secret sauce is line 4, where we guard against the possibility that the LoginViewController was deallocated prior to the API request completing. If
self no longer exists, it doesn’t care about the API result, and we can bail out of the closure right away. We simply use the
strongSelf reference for the remainder of the closure to avoid unwrapping
self at every step and we’re good to go!