Capture List In Swift


ios, swift

Capture List In Swift


Daniel Shin - May 06, 2015

This is a sequel to the ARC In Swift.

Capture list is used in closure declaration to capture references to the variables outside of closure context in order to execute in the future even when such context becomes out of scope.

Most of the times, capture list is unnecessary since Swift automatically captures all variables used inside closure.

It captures by strongly referencing the given objects in memory, and thus keeping them in memory as long as the closure context is yet to finish executing.

However, in some cases, this introduces Strong Reference Counting as noted in previous article.

Capture list solves this problem by letting the programmer to optionally override the default capture reference type (strong reference) with either unowned or weak.

Capture weak reference inside closure context

I recently discovered the danger of ‘capturing’ through weak reference. When you specify weak / unowned reference in capture list, it doesn’t create any hold onto the given object in memory, but rather it remembers its memory address so that it can reference it at later time. This makes no guarantee of that given object actually being present at the time of memory access.

Usually, with unowned reference, you will be more careful, you should really, since it will crash your program if the reference points to nil.

However, you should also need to be careful with weak reference.

var someClosure = { [weak someVariable] in
  if someVariable == nil { return }
  
  doSomething(someVariable!)
}

This kind of pattern should be familiar. You check if the given optional variable is nil, and if so return, otherwise continue using that variable since it is verified to be non-nil.

But is it really?

This is true for strongly referenced variables.

However, remember that weak variable doesn’t enforce the presence of object in memory. That object in memory is only as long as in memory some other variable that ‘strongly’ points to that object is in context and as far as this weak variable is concerned, it has no control over when that object in memory might be garbage collected.

What if that someVariable was garbaged collected immediately after if someVariable == nil { return }? By force unwrapping it in second statement, your app will crash. This is very hard to debug since it will crash haphazardly on runtime. Sometimes, it will be retained as far as the second statement executes, some other rare times, it wont.

Simple solution to this is to simply use if let pattern.

var someClosure = { [weak someVariable] in
  if let capturedSomeVariable = someVariable {
    doSomething(capturedSomeVariable)
  }
}

if let will create a new capturedSomeVariable by strongly referencing it (all variable declaration by default uses strong reference unless explicitly told otherwise).

weak variable someVariable is strongly referenced by capturedSomeVariable. This means someVariable will be kept alive in memory as long as capturedSomeVariable is relevant. This simply solution elegantly solves edge cases introduced by the first pattern.

If you are not sure, always use if let. Yeah it might make your code look less clean with possible pyramid hell but it’s better than crashing your app haphazardly. Guaranteed crash is easy to debug. Haphazard crash is nearly impossible to debug.