Swiftwater Logo

Closures, Context and Capture

This entry is part 6 of 21 in the series Swiftwater

ABSTRACT

Swift closures are an important part of the language; especially with the OBSERVER Pattern in so much use (Apple has a specialization that they call the Delegate Pattern).

It’s very often most convenient to create a lightweight, disposable function on the spot, without the need for the infrastructure and overhead of creating an “official” method or named function.

However, there are important questions about context, and possible ramifications of using closures. These are not always obvious, so we’ll walk through the process of working with closures in this post.

THE BASICS

I won’t go through the various “shorthand” syntax options for closures, but you should definitely understand them.

Apple has covered this fairly well in their documentation.

In the examples here, I’ll usually be using the most efficient (read: “not so easy to understand”) syntax.

A BASIC GLOBAL-SCOPE EXAMPLE

The idea of scope is pretty critical in a closure, and not at all obvious. What variables can we access outside the closure? What, exactly, will be passed in, and when?

Also, can we depend on our external variable, objects and values to be constant? Can we depend on our external entities to update to reflect current conditions? Do we have the ability to control this stuff?

The answer to the first couple is “it depends.” The answer to the last few questions is “yes.”

In the following example, we’ll answer some of them.

Scope Mouthwash

Scope

In any language, the concept of scope is critical. In most cases, it’s a “no brainer,” and fairly obvious.

Explicit Scope

Swift, like most C-based languages, uses braces ({...}) to delimit scope. Things between the braces can access each other. Entities outside the braces can’t access values inside the braces, and things inside the braces may, or may not, be able to access things outside the braces (usually, yes).

Also, the order of things can affect what is available in a scope. You may (or may not -don’t you just LOVE ambiguity?) only be able to access things like declared variables after they have been declared in a scope.

Classes and structs use braces to declare their scopes:

class SomeClass {
    var thisVariableIsInTheClassScope: Int = 0
            ·
            ·
            ·
    func someClassMethod() {
        let someValue = thisVariableIsInTheClassScope
            ·
            ·
            ·
    }
            ·
            ·
            ·
    func someOtherClassMethod() {
        let someValue = self.thisVariableIsInTheClassScope
        let thisVariableIsInTheClassScope = self.thisVariableIsInTheClassScope
            ·
            ·
            ·
    }
            ·
            ·
            ·
}

let someValue = instanceOfSomeClass.thisVariableIsInTheClassScope

In the above example, in order to access something inside the class scope, an assignment outside the scope needs to explicitly declare the scope (the class instance/object) in order to access the variable inside the class scope.

Internal class methods usually don’t need to specify the scope (self), unless they are re-using the same name somewhere within the method scope (Remember that the method uses braces to establish its own scope).

Scopes allow encapsulation of the values, methods/functions and names.

Global Scope

Global scope is the “implied” scope that each file produces. In Swift, this is generally a “project global” scope, with all entities at that level able to access other entities in other files. The exact mechanics and rules governing this scope are outside the…erm…”scope” of this post. Here’s some light reading on the matter.

It’s generally simplest to use the global scope for examples, but, in real life, we usually try to stay the hell out of global scope.

Declaring A Simple Closure

We’ll start by declaring a simple closure that accesses a global-scope variable, and prints it:

var count = 0

let closure = {print("count is \(count)")}

for counter in 0..<5 {
    count = counter
    closure()
}

Running this bit of code in a playground gives you this in the console:

count is 0
count is 1
count is 2
count is 3
count is 4

A couple of things are obvious from this example:

  1. The closure seems to be able to access variables outside its scope. Note that it could access the global count variable while it was running.
  2. This appears to be a real-time access. It doesn’t just take a “snapshot” of the global variable, and reuse it. The current state of the global variable is accessed at closure runtime.

That pretty much jives with Swift’s context and scope rules.

Capture the Flag

Now, suppose we wanted to “capture” that count value, and “detach” it from the “live” variable?

There’s a number of reasons that we might want to do this. For example, we may have the global variable in an optional that is cleared before the closure runs:

var optionalCount: Int! = 0

let optionalClosure = {print("optionalCount is \(optionalCount)")}
for counter in 0..<5 {
    optionalCount = counter
    optionalClosure()
}

Outputs this to the console:

optionalCount is Optional(0)
optionalCount is Optional(1)
optionalCount is Optional(2)
optionalCount is Optional(3)
optionalCount is Optional(4)

Now, let’s throw a spanner into the works:

var optionalCount: Int! = 0

let optionalClosure = {print("optionalCount is \(optionalCount)")}
for counter in 0..<5 {
    optionalCount = counter
    optionalCount = nil
    optionalClosure()
}

Outputs this to the console:

optionalCount is nil
optionalCount is nil
optionalCount is nil
optionalCount is nil
optionalCount is nil

Oh, dear.

Suppose we were to “capture” the value of the optionalCount variable, and “detach” it?

Swift gives us the ability to do this. They call it “Capturing” values. It isn’t unique to Swift, but it isn’t the most well-understood concept.

Swift allows you to create an explicit array, just before the function parameter list in a closure. The array contains the variables and entities that we want the function to access while running. It doesn’t contain Strings. It contains the actual values (and references -more on that later); like so:

var optionalCount: Int! = 0

let optionalClosure = {[optionalCount] in print("optionalCount is \(optionalCount)")}

// or...
// let optionalClosure = {[optionalCount]() -> Void in print("optionalCount is \(optionalCount)")}

for counter in 0..<5 {
    optionalCount = counter
    optionalCount = nil
    optionalClosure()
}

The in is now required, because we need to separate the declaration from the function body in the closure.

Running the playground now, gives us:

optionalCount is Optional(0)
optionalCount is Optional(0)
optionalCount is Optional(0)
optionalCount is Optional(0)
optionalCount is Optional(0)

What happened here, is that the value of the optionalCount variable was “captured,” at the time the closure was created. Instead of a reference that reaches outside the closure scope to access a variable in the global scope, a copy of that was made to a local variable inside the closure, so it doesn’t matter what the global variable does.

This Array says “Instead of directly accessing the variables in the container scope, make a local copy of the variable, and let me use that in the closure.”

Values and References

Our example featured an Int type. These are “pass-by-value” types. That means that when you assign an instance of Int to another Int, a copy of that Int is made and written into the new instance. So that means that the instance we included in the Array (and it is an Array) in the closure declaration was actually a copy of the optionalCount variable. That makes the “capture” pretty straightforward.

But what about “pass-by-reference” types, like classes?

Let’s fire up the ol’ playground, and see for ourselves:

class ReferencedClass { var integerValue: Int = 5 }

var referencedClass: ReferencedClass! = ReferencedClass()

let optionalClosure3 = {[referencedClass] in print("referencedClass.integerValue is \(referencedClass!.integerValue)")}
for counter in 0..<5 {
    referencedClass!.integerValue = counter
    optionalClosure3()
}

We created a very simple class, with nothing but an Int property. We reference that from our closure, and access the Int property. Running that gives us a console output of:

referencedClass.integerValue is 0
referencedClass.integerValue is 1
referencedClass.integerValue is 2
referencedClass.integerValue is 3
referencedClass.integerValue is 4

Note that the behavior is different from the last captured variable. Instead of a steady stream of “0” (actually, in this case, it would be “5”), we now have the variable counting up, just like in the first closure example.

This tells us that we are now accessing the “live” global entity. We no longer have a local copy of it.

Note that we declared the instance using an implicit optional (!). There’s a reason for that, which we’ll get to in just a bit.

Remember that classes are accessed by reference, not by value. This means that our “local copy” isn’t really a “copy.” It’s a reference to an external instance.

Now… that implicit optional…

We’ll break out that ol’ monkey wrench, and toss it into the mechanism:

let optionalClosure4 = {[referencedClass] in print("referencedClass.integerValue is \(referencedClass!.integerValue)")}
referencedClass = nil
for counter in 0..<5 {
    referencedClass!.integerValue = counter
    optionalClosure4()
}

Ooohhh… That’s gonna leave a mark.

Of course it’s not gonna work. You can’t reference a nil optional.

However, there was something strange about the error. The error only was reported against the referencedClass!.integerValue = counter line. It wasn’t reported against the internal closure access, which you would think would be nil, as well.

It’s probably just because we ran into the assignment error first. Let’s remove that from the table, and see what we get:

let optionalClosure4 = {[referencedClass] in print("referencedClass.integerValue is \(referencedClass!.integerValue)")}
referencedClass = nil
for counter in 0..<5 {
    optionalClosure4()
}

Whiskey Tango Foxtrot! Look what we got!

referencedClass.integerValue is 5
referencedClass.integerValue is 5
referencedClass.integerValue is 5
referencedClass.integerValue is 5
referencedClass.integerValue is 5

Weren’t we supposed to get an exception for referencing a nil value there?

Yeah…well about that “nil value”…

Remember that Swift uses an internal reference counter to govern the lifecycles of its objects. Every time an object (instance) is referenced, the reference count increments by one.

The initial var referencedClass: ReferencedClass! = ReferencedClass() assignment/instantiation was one reference, and the capture was a second reference.

Remember that being in that Array creates a “hidden” local variable in the closure scope, so we now have another lifeline to the object that keeps it alive. We nuked our original global scope reference, but we still have the transient reference in the closure.

The closure is printing the current state of the global scope object, which isn’t being changed anymore.

Let’s do something about that:

referencedClass = ReferencedClass()
let optionalClosure5 = {[referencedClass] in
    print("referencedClass.integerValue is \(referencedClass!.integerValue)")
    referencedClass!.integerValue += 1
}
referencedClass = nil
for counter in 0..<5 {
    optionalClosure5()
}

Running this gives us:

referencedClass.integerValue is 5
referencedClass.integerValue is 6
referencedClass.integerValue is 7
referencedClass.integerValue is 8
referencedClass.integerValue is 9

Remember that the class is set to begin at 5, so that’s where we started.

We added the increment to the place where we still have a reference to the object.

Leak

Houston, We Have A Problem

What could be a problem? We know that we have a “live” object; we just don’t have that global reference anymore! The code runs fine, and gives us the result we want!

Um…in the vernacular, we refer to this as “a leak.”

Now, there’s two ways we can deal with this:

  1. We can make the closure instance optional, so we can delete it. That should remove its “captured” reference.
  2. We can make the reference in the capture list weak.

Let’s try Solution 1, first. We’ll create a fancier harness to allow us to track instances:

var referenceCounter: Int = 0

class ReferencedClass2 {
    var integerValue: Int = 6
    init() { referenceCounter += 1 }
    deinit { referenceCounter -= 1 }
}

This will allow us to count how many times the instance was instantiated (not “referenced” -instantiated. REALLY primitive, I know, but that’s all we need for now. We just need a 1 or a 0).

Next, we’ll use this in our example:


var referenceCounter: Int = 0

class ReferencedClass2 {
    var integerValue: Int = 6
    init() { referenceCounter += 1 }
    deinit { referenceCounter -= 1 }
}

var referencedClass3: ReferencedClass2! = ReferencedClass2()

var optionalClosure6: (() -> Void)! = {[referencedClass3] in
    print("referencedClass.integerValue is \(referencedClass3!.integerValue)")
    referencedClass3!.integerValue += 1
}

referencedClass3 = nil
for counter in 0..<5 {
    optionalClosure6()
}
optionalClosure6 = nil

print("referenceCounter is \(referenceCounter)")

Which gives us this:

referencedClass.integerValue is 6
referencedClass.integerValue is 7
referencedClass.integerValue is 8
referencedClass.integerValue is 9
referencedClass.integerValue is 10
referenceCounter is 0

That worked, but it was a bit awkward.

Now, lets try the original method, but using a weak reference:

referenceCounter = 0

var referencedClass4: ReferencedClass2! = ReferencedClass2()

var optionalClosure7: (() -> Void)! = {[weak referencedClass4] in
    print("referencedClass.integerValue is \(referencedClass4!.integerValue)")
    referencedClass4!.integerValue += 1
}

referencedClass4 = nil
for counter in 0..<5 {
    optionalClosure7()
}

print("referenceCounter is \(referenceCounter)")

BOOM! We get an error the first time we do this:

optionalClosure7()

The weak reference was “weak,” all right. The object disappeared as soon as we set the global reference to nil. Let’s nil it after we’re done with it:

referenceCounter = 0

var referencedClass4: ReferencedClass2! = ReferencedClass2()

var optionalClosure7: (() -> Void)! = {[weak referencedClass4] in
    print("referencedClass.integerValue is \(referencedClass4!.integerValue)")
    referencedClass4!.integerValue += 1
}

for counter in 0..<5 {
    optionalClosure7()
}
referencedClass4 = nil

print("referenceCounter is \(referenceCounter)")

That’s better:

referencedClass.integerValue is 6
referencedClass.integerValue is 7
referencedClass.integerValue is 8
referencedClass.integerValue is 9
referencedClass.integerValue is 10
referenceCounter is 0

CONCLUSION

That was quite a handful for one post, but we’re not really done with this topic yet.

We discussed what scope is, and how it applies to closures.

We introduced the concept of capture lists, and covered their most basic uses.

We covered the ramifications of value vs. reference in capture lists.

We pointed out that it’s easy to create leaks with improperly-handled capture lists.

The next post will explore this concept even more.


SAMPLE PLAYGROUND

NOTE: Although the code discussed above was workshopped with this playground, it was not a verbatim copy.

var count = 0

print("\nSimple Closure (No capture list):\n")
let closure = {print("count is \(count)")}
for counter in 0..<5 {
    count = counter
    closure()
}

var optionalCount: Int! = 0

print("\nSimple Closure (No capture list, optional global):\n")
let optionalClosure = {print("optionalCount is \(optionalCount)")}
for counter in 0..<5 {
    optionalCount = counter
    optionalClosure()
}

print("\nSimple Closure (No capture list, optional global -clear optional):\n")
for counter in 0..<5 {
    optionalCount = counter
    optionalCount = nil
    optionalClosure()
}

optionalCount = 5

print("\nSimple Closure (With capture list, optional global -clear optional):\n")
let optionalClosure2 = {[optionalCount] in print("optionalCount is \(optionalCount)")}
for counter in 0..<5 {
    optionalCount = counter
    optionalCount = nil
    optionalClosure2()
}

class ReferencedClass { var integerValue: Int = 5 }

var referencedClass: ReferencedClass! = ReferencedClass()

print("\nSimple Closure (With capture list, optional global reference value):\n")
let optionalClosure3 = {[referencedClass] in print("referencedClass.integerValue is \(referencedClass!.integerValue)")}
for counter in 0..<5 {
    referencedClass!.integerValue = counter
    optionalClosure3()
}

print("\nSimple Closure (With capture list, optional global reference value -clear optional):\n")
let optionalClosure4 = {[referencedClass] in print("referencedClass.integerValue is \(referencedClass!.integerValue)")}
referencedClass = nil
for counter in 0..<5 {
    // This is an error. You can't access a nil optional.
    //    referencedClass!.integerValue = counter
    optionalClosure4()
}

var referenceCounter: Int = 0

class ReferencedClass2 {
    var integerValue: Int = 6
    init() { referenceCounter += 1 }
    deinit { referenceCounter -= 1 }
}

var referencedClass2: ReferencedClass2! = ReferencedClass2()

print("\nSimple Closure (With capture list, optional global reference value -clear optional, but incremented internally):\n")
let optionalClosure5 = {[referencedClass2] in print("referencedClass.integerValue is \(referencedClass2!.integerValue)"); referencedClass2!.integerValue += 1}
referencedClass2 = nil
for counter in 0..<5 {
    optionalClosure5()
}

print("referenceCounter is \(referenceCounter)")

referenceCounter = 0

var referencedClass3: ReferencedClass2! = ReferencedClass2()

print("\nSimple Closure (With capture list, optional global reference value -clear optional, but incremented internally. Optional closure):\n")
var optionalClosure6: (() -> Void)! = {[referencedClass3] in print("referencedClass.integerValue is \(referencedClass3!.integerValue)"); referencedClass3!.integerValue += 1}
referencedClass3 = nil
for counter in 0..<5 {
    optionalClosure6()
}
optionalClosure6 = nil

print("referenceCounter is \(referenceCounter)")

referenceCounter = 0

var referencedClass4: ReferencedClass2! = ReferencedClass2()

print("\nSimple Closure (With capture list, optional global reference value -clear optional, but incremented internally. Optional closure with weak reference):\n")
var optionalClosure7: (() -> Void)! = {[weak referencedClass4] in print("referencedClass.integerValue is \(referencedClass4!.integerValue)"); referencedClass4!.integerValue += 1}
// This will be an error. We need to wait until we're done.
//referencedClass4 = nil
for counter in 0..<5 {
    optionalClosure7()
}
referencedClass4 = nil

print("referenceCounter is \(referenceCounter)")

print("\nClosure with Capture List Closure (count is captured at 4 at closure instantiation):\n")
let closure0 = {[count] in print("count is \(count)")}
for counter in 0..<5 {
    count = counter
    closure0()
}

count = 955
var illRatOutCount: Int = 0

print("\nClosure with Capture List Closure (count is set to 955 at capture time, and illRatOutCount is not captured):\n")
let closure955 = {[count] in print("count is \(count) and illRatOutCount is \(illRatOutCount)")}
for counter in 0..<5 {
    count = counter
    illRatOutCount = count
    closure955()
}

count = 1024

print("\nClosure with Capture List Closure (count is set to 1024, and illRatOutCount is set to 4 -the last value of count- at capture time):\n")
let closure1024 = {[count, illRatOutCount] in print("count is \(count) and illRatOutCount is \(illRatOutCount)")}
for counter in 0..<5 {
    count = counter
    illRatOutCount = count
    closure1024()
}

count = 3

print("\nClosure with Capture List Closure (capture is done inside the list, so you get the current values -however, illRatOutCount is set after the capture, so you see these values after the fact):\n")
for counter in 0..<5 {
    count = counter
    let closure3 = {[count, illRatOutCount] in print("count is \(count) and illRatOutCount is \(illRatOutCount)")}
    illRatOutCount = count
    closure3()
}

count = 0

// The following is an error. You can only capture a variable if it is in context.
//print("\nClosure with Capture List Closure (count is set to index at capture time):\n")
//let closureCounter = {[counter] in print("count is \(count)")}
//for counter in 0..<5 {
//    count = counter
//    closureCounter()
//}