The Weak/Strong Dance in Swift
We’re all familiar with the “weak/strong dance” in Objective-C. But I am wondering what the best practice is for doing it in Swift.
For some context, here’s a (somewhat contrived) example where you need to
capture a weak
reference in a closure, otherwise you get a retain cycle.
class C {
let name: String
var block: (() -> Void)?
init(name: String) {
self.name = name
block = {
self.doSomething()
}
}
deinit { print("Destroying \(name)") }
func doSomething() { print("Doing something for \(name)") }
}
var c = C(name: "one")
print(c.name)
c = C(name: "two")
print(c.name)
Output:
one
two
Note that deinit
never happens for the first object (because “Destroying one”
was never printed), even when c
is changed to point at a new instance. That’s
because of the retain cycle.
Swift’s capture lists do a nice
job of letting you only capture self
weakly inside a closure, to avoid the
retain cycle. That’s half the battle.
Using the Capture List
class C {
let name: String
var block: (() -> Void)?
init(name: String) {
self.name = name
block = { [weak self] in // <-- Here is the change
self?.doSomething()
}
}
deinit { print("Destroying \(name)") }
func doSomething() { print("Doing something for \(name)") }
}
var c = C(name: "one")
print(c.name)
c = C(name: "two")
print(c.name)
Now it outputs:
one
Destroying one
two
Much better.
Using self
inside the Closure
A nice detail about using [weak self]
is that self
becomes an Optional
inside of the closure. You can see this by the fact that we had to change to
self?.doSomething()
in the preceding example.
However, what if you have multiple steps that want to use self
inside the
closure. Since it’s a weak reference, it can go away at any time, even between
2 subsequent uses. Here’s an example, with a slightly different set up, to
focus on the closure.
class C {
deinit { println("Destroying C") }
func log(msg: String) { println(msg) }
func doClosure() {
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
self?.log("before sleep")
usleep(500)
self?.log("after sleep")
}
}
}
var c: C? = C() // Optional, so we can set it to nil
c?.doClosure()
dispatch_async(dispatch_get_global_queue(0, 0)) {
usleep(100)
c = nil // This will dealloc c
}
dispatch_main()
Output:
before sleep
Destroying C
It doesn’t print after sleep
, because self?
is nil by then.
This can lead to some very subtle and hard-to-find bugs. So, it’s common to
convert the reference back to a strong one inside the closure, to make sure that
once the closure starts executing, that self
will stay alive until the end.
In fact, clang even has warnings if you forget to do this in Objective-C.
But, as I mentioned at the start of this post, I’m not sure what the best practice is in Swift for this “back to strong” part.
A Few Ideas to get a Strong Reference
Use if let
func doClosure() {
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
if let strongSelf = self { // <-- This is the interesting part
strongSelf.log("before sleep")
usleep(500)
strongSelf.log("after sleep")
}
}
}
// or in Swift 2, using `guard let`:
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
guard let strongSelf = self else { return } // <-- This is the interesting part
strongSelf.log("before sleep")
usleep(500)
strongSelf.log("after sleep")
}
Output:
before sleep
after sleep
Destroying C
Pros:
- It’s pretty obvious what is going on.
- Get back to non-optional type for the local variable inside the closure.
Cons:
- Unfortunately, we can’t do
if let self = self
, because you can’t assign toself
(although, this would work nicely if the variable you were capturing wasn’tself
). So, then we end up withstrongSelf
inside the closure, which is pretty ugly, and we have to use that as our local variable everywhere inside the closure.- Although, at least Swift affords us some warning if we forget to use
self
instead ofstrongSelf
inside the closure, because the type ofself
is an optional, instead of the non-optional that we want. For comparison, Objective-C doesn’t give any warning for that, and it’s an easy thing to accidentally get wrong.
- Although, at least Swift affords us some warning if we forget to use
Use withExtendedLifetime
Swift’s standard library has a function called withExtendedLifetime()
, that is
similar in spirit to what we’re trying to accomplish here.
/// Evaluate `f()` and return its result, ensuring that `x` is not
/// destroyed before f returns.
func withExtendedLifetime<T, Result>(x: T, @noescape _ f: () -> Result) -> Result
So, we could use that instead:
func doClosure() {
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] in
withExtendedLifetime(self) {
self!.log("before sleep")
usleep(500)
self!.log("after sleep")
}
}
}
Pros:
- You don’t have to use
strongSelf
in the closure.
Cons:
- You’re left with an optional
self
, so you have to unwrap it.
Make a custom withExtendedLifetime()
A suggestion from @jtbandes was to make a
custom withExtendedLifetime()
that passes the value to the closure it takes:
extension Optional {
func withExtendedLifetime(body: T -> Void) {
if let strongSelf = self {
body(strongSelf)
}
}
}
// Then:
func doClosure() {
dispatch_async(dispatch_get_global_queue(0, 0)) { [weak self] () -> Void in
self.withExtendedLifetime {
$0.log("before sleep")
usleep(500)
$0.log("after sleep")
}
return
}
}
Pros:
- Follows naming conventions set by the standard library.
Cons:
- Still have to use a custom local variable name inside the closure (although,
the default
$0
seems not terrible for this) . - In this case, I had to add some extra type info to the
dispatch_async()
closure. I’m not totally sure why.
Other?
If you have any better ideas, let me know!.