- FURTHER DOWN THE RABBIT-HOLE
- When Something is “Captured,” Can We Affect it Outside of Our Closure Scope?
- I see your global-context closure, and raise you a class-bound closure.
- What About “
self
“? How Does That Factor Into a Capture Scenario? - What Context Governs the captured entities? Declaration, Instantiation or Execution?
- Can We Use “unowned” Instead of “weak”?
- CONCLUSION
- SAMPLE PLAYGROUND
ABSTRACT
In the last post of the series, we had established the basic mechanics of Closures and Capture Lists.
A Capture List is specified as an Array of Any ([Any]
), inserted just before the parameter list of a closure, like so:
var externalContextVariable: Int = 0
let externalContexClass: SomeClass! = SomeClass()
let closure = {[weak externalContexClass] () -> Void in doSomethingHere() }
This Array has to reference entities available to the closure scope.
You can use modifiers like weak to prevent the captured entity from adding a new reference to a class instance.
FURTHER DOWN THE RABBIT-HOLE
I don’t know about you, but the discussion left me with a few questions:
- When something is “captured,” can we affect it outside of our closure scope?
- I see your global-context closure, and raise you a class-bound closure.
- What about “
self
“? How does that factor into a capture scenario? - What context governs the captured entities? Declaration, Instantiation or Execution?
- Can we use “unowned” instead of “weak”?
These are good questions, and we’ll explore each below.
When Something is “Captured,” Can We Affect it Outside of Our Closure Scope?
Basically, when we capture an entity, is it “read-only” or “read/write” (“let
” vs. “var
“)? We already know that we can affect external entities if we don’t capture them (assuming they are var
already).
Let’s see what the playground says:
We know this works (We don’t try to affect the captured variable):
var externalContextVariable: Int = 5 let closure = {[externalContextVariable] () -> Void in print("externalContextVariable is \(externalContextVariable)") } closure()
And we also know this works (We don’t capture the variable):
var externalContextVariable: Int = 5
let closure2 = {
externalContextVariable = 6
print("externalContextVariable is \(externalContextVariable)")
}
closure()
What about this (We capture the variable, and try to affect it from within the closure)?
var externalContextVariable: Int = 5
let closure = {[externalContextVariable] () -> Void in
externalContextVariable = 6
print("externalContextVariable is \(externalContextVariable)")
}
closure()
No dice. Syntax error. We get “error: cannot assign to value: 'externalContextVariable' is an immutable capture
“.
Well, that answers that question. Anything in the closure list is a “let
,” which pretty much makes sense, when you consider how it works.
Which, of course, begs another question:
Can we coerce these into read/write?
How about this?
var externalContextVariable: Int = 5
let closure = {[var externalContextVariable] () -> Void in
externalContextVariable = 6
print("externalContextVariable is \(externalContextVariable)")
}
closure()
Nope. “error: expected 'weak', 'unowned', or no specifier in capture list
“. That doesn’t seem to bode well for our experiment, but we’re stubborn. Let’s try one more thing:
var externalContextVariable: Int = 5
let closure = {[inout externalContextVariable] () -> Void in
externalContextVariable = 6
print("externalContextVariable is \(externalContextVariable)")
}
closure()
Nope. Same deal.
Answer:
All entities in a capture list are read-only. They are the equivalent of “let
.” We already know that references will still reference outside entities, but the references, themselves are read-only.
I see your global-context closure, and raise you a class-bound closure.
Don’t you just hate it when some smug know-it-all makes some huge, sweeping assertion, and “proves” it with ten lines of simple, procedural code in the global context?
Those of us who have to deal with thousands of lines of code -often badly written- -by someone else who has left the company, and didn’t speak English anyway- know that there’s precious few real-world scenarios that can be addressed with hundred-line-or-less academic boilerplate.
Of course, everything here is basically academic boilerplate, but I feel your pain. I’ve been writing production code since you had to manually clock in a boot sequence with switches.
You kids don’t know how good you have it. We used to have to walk to and from school, through two feet of snow -winter AND summer-, every day, uphill, both ways, while carrying 50 pounds of groceries home in our backpacks…HEY, KIDS! GET OFF MY LAWN!
But I digress. Where were we? Oh, yes. Let’s explore using class-bound closures.
Let’s add this to our playground:
class SomeClass { var myString: String = "" var myInt: Int = 0 func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void { self.myString = stringValue self.myInt = intValue return {print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")} } } SomeClass().spewOutAClosure(intValue: 3, stringValue: "Initial String")()
This will print the following to the console:
externalContextVariable is 6 self.myInt is 3 self.someString is "Initial String"
We didn’t bother assigning the class instance to a variable, because we’re just testing it. It works.
externalContextVariable
was set to 6 by a previous step.
Now we can move on to the next questions.
What About “self
“? How Does That Factor Into a Capture Scenario?
We already know, from page one of this exploration, that we can nuke a class instance before executing a closure that references it.
The closure capture list deals with it by adding a strong reference to the class instance, or the closure creates a strong reference if no capture list was specified.
However, we can mitigate this by declaring the reference as “weak
” in the capture list, and testing for nil before unwrapping it.
That said, all the examples we’ve seen so far have external instance references. What happens if the reference is to “self
“?
OK. In order to save room, I’ll leave the above declaration part out.
externalContextVariable = 10 var oneInstanceOfSomeClass: SomeClass! = SomeClass() var oneInstanceOfAClassBoundClosure: () -> Void = oneInstanceOfSomeClass.spewOutAClosure(intValue: 1000, stringValue: "One Thousand") oneInstanceOfAClassBoundClosure()
Will output the following to the console:
externalContextVariable is 10 self.myInt is 1000 self.someString is "One Thousand"
If we add the following:
externalContextVariable = 10
var oneInstanceOfSomeClass: SomeClass! = SomeClass()
var oneInstanceOfAClassBoundClosure: () -> Void = oneInstanceOfSomeClass.spewOutAClosure(intValue: 1000, stringValue: "One Thousand")
oneInstanceOfAClassBoundClosure()
oneInstanceOfSomeClass.myInt = 20
oneInstanceOfSomeClass.myString = "Twenty"
oneInstanceOfAClassBoundClosure()
We get:
self.myInt is 1000
self.someString is "One Thousand"
externalContextVariable is 10
self.myInt is 20
self.someString is "Twenty"
Which is pretty much exactly what we expect.
Do We Need That Internal “self.
” in the Closure?
return {print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")}
Yes. Closures have a rule that says that we need to explicitly use “self.
” when accessing class-bound properties. It’s designed to keep us aware that we are accessing a property.
Can We Capture “self
” in the Capture List?
No. If you try this:
return {[self] in print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")}
You will get an error that says “error: expected 'weak', 'unowned', or no specifier in capture list
“.
How About Weakly?
Yes. This will work:
return {[weak self] in print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self!.myInt)\nself.someString is \"\(self!.myString)\"")}
Note that I also needed to add unwrappers to the property accessors. Declaring it “weak” automatically made the captured entity optional.
How About Unowned?
Again, yes. This time, the captured entity is not optional, so the unwrappers aren’t necessary:
return {[unowned self] in print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")}
I should note that one reason that you’d want to use a weak or unowned self reference, is that a strong self reference in a closure can cause an object to stick around past its “sell by” date (otherwise known as “a leak”).
If you delete a closure instance with a strong reference (to self or otherwise), then it will delete that reference. However, you need to make sure the closure is actually deleted, and not simply swept under a rug, somewhere.
Now, let’s add a couple more classes to our playground to test this stuff a bit further:
class SomeClassWeakOverride: SomeClass { override func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void { self.myString = stringValue self.myInt = intValue return {[weak self] in print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self!.myInt)\nself.someString is \"\(self!.myString)\"")} } } class SomeClassUnownedOverride: SomeClass { override func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void { self.myString = stringValue self.myInt = intValue return {[unowned self] in print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")} } }
The only difference with these two classes is that each has a qualified capture list in the returned closure.
Now, we’ll explore the differences between the three classes.
First, lets’ go back to the original strong reference, and create an instance of that:
oneInstanceOfSomeClass = SomeClass()
oneInstanceOfAClassBoundClosure = oneInstanceOfSomeClass.spewOutAClosure(intValue: 10, stringValue: "Ten")
oneInstanceOfAClassBoundClosure()
oneInstanceOfSomeClass.myInt = 15
oneInstanceOfSomeClass.myString = "Fifteen"
oneInstanceOfSomeClass = nil
oneInstanceOfAClassBoundClosure()
Which outputs:
externalContextVariable is 10 self.myInt is 10 self.someString is "Ten" externalContextVariable is 10 self.myInt is 15 self.someString is "Fifteen"
In this case, we nilled our global context instance, but the closure execution after the nil worked anyway. That’s because of the implicit strong reference to “self
” in the closure.
Now, let’s try it with a weak reference closure:
var anotherInstanceOfSomeClass: SomeClassWeakOverride! = SomeClassWeakOverride()
var anotherInstanceOfAClassBoundClosure = anotherInstanceOfSomeClass.spewOutAClosure(intValue: 10, stringValue: "Ten")
anotherInstanceOfAClassBoundClosure()
anotherInstanceOfSomeClass.myInt = 15
anotherInstanceOfSomeClass.myString = "Fifteen"
anotherInstanceOfSomeClass = nil
anotherInstanceOfAClassBoundClosure()
Which outputs:
externalContextVariable is 10
self.myInt is 10
self.someString is "Ten"
Fatal error: Unexpectedly found nil while unwrapping an Optional value
YOW! It popped its clogs before we even got to the assignment lines! What happened?
Swift was smart enough to know that the instance wasn’t necessary after the first closure call, even though we accessed it to set the property values. Remember that the closure reference is weak, so the compiler said “Yeah…we’re done here.”, and took the object behind the woodshed, right after the last time it was used to actually do something useful.
If we added another call, just before we nuked it, it would be fine (sort of):
var anotherInstanceOfSomeClass: SomeClassWeakOverride! = SomeClassWeakOverride()
var anotherInstanceOfAClassBoundClosure = anotherInstanceOfSomeClass.spewOutAClosure(intValue: 10, stringValue: "Ten")
anotherInstanceOfAClassBoundClosure()
anotherInstanceOfSomeClass.myInt = 15
anotherInstanceOfSomeClass.myString = "Fifteen"
anotherInstanceOfAClassBoundClosure()
anotherInstanceOfSomeClass = nil
anotherInstanceOfAClassBoundClosure()
We now get:
externalContextVariable is 10
self.myInt is 10
self.someString is "Ten"
externalContextVariable is 10
self.myInt is 15
self.someString is "Fifteen"
Fatal error: Unexpectedly found nil while unwrapping an Optional value
We still get a fatal error, but now it’s where we expect, inside the closure. The weak reference has made the internal reference go nil, so trying to unwrap it in the print statement now borks. We knew that was gonna happen.
We can address that by making the following change to the closure:
class SomeClassWeakOverride: SomeClass {
override func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void {
self.myString = stringValue
self.myInt = intValue
return {[weak self] in
if nil != self {
print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self!.myInt)\nself.someString is \"\(self!.myString)\"")
} else {
print("self is nil!")
}
}
}
}
Which now gives us:
externalContextVariable is 10 self.myInt is 10 self.someString is "Ten" externalContextVariable is 10 self.myInt is 15 self.someString is "Fifteen" self is nil!
And no error.
“Unowned” is similar, except we wouldn’t be able to do that test for nil. There would be no way to avoid the crash. You’ll just have to make sure that the instance stays alive until we’re done with it.
What Context Governs the captured entities? Declaration, Instantiation or Execution?
This one’s easy. Instantiation. The capture is made at the time the closure is actually created.
Here’s a quick test:
var contextCheck: String = "Declaration" // This is the declaration phase of the closure. It is declared, but not instantiated. func generateAClosureForMe() -> () -> String { return {[contextCheck] in return contextCheck} // Note that we capture the parameter. } contextCheck = "Instantiation" // This is the instantiation phase. We ask the factory function to give us an instance of the closure. let myClosure = generateAClosureForMe() contextCheck = "Execution" // This is the execution phase. We run the closure here. print("The state was captured during the " + myClosure() + " phase.")
This outputs the following to the console.
The state was captured during the Instantiation phase.
However, if we had not captured the variable, it would have happened during the execution phase:
contextCheck = "Declaration" // This is the declaration phase of the closure. It is declared, but not instantiated. func generateAClosureForMe2() -> () -> String { return {return contextCheck} // Note that we don't capture the parameter. } contextCheck = "Instantiation" // This is the instantiation phase. We ask the factory function to give us an instance of the closure. let myClosure2 = generateAClosureForMe2() contextCheck = "Execution" // This is the execution phase. We run the closure here. print("The state was captured during the " + myClosure2() + " phase.")
The state was captured during the Execution phase.
Can We Use “unowned” Instead of “weak”?
Yes. We already covered this above.
The principal difference between “weak
” and “unowned
” is that weak references MUST be optional, because the compiler actually sets the reference, itself, to nil when the referenced object is deallocated. You can test weak references to see if they are valid.
Unowned references are not affected by the state of their targets, so they are never set to nil. You can’t tell if the target of an unowned reference is valid or not.
Apple’s guidance on the issue is that if the target of a reference will never become nil, then you should use “unowned
,” as opposed to “weak
.” This is because “weak
” has more runtime overhead.
I take it a bit farther. I say that if it doesn’t matter whether or not the target is nil, use an unowned reference. Say you reference an object during a part of the program where we absolutely know that it’s good. We then don’t care. In that case, I would use an unowned reference.
CONCLUSION
That brings us…closure…on closures. They are an important aspect of Swift, and often poorly understood.
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.
We talked about whether or not we can affect the targets of captured elements (no, we can’t)
We introduced class-bound closures.
We discussed how we deal with “self
” in class-bound closures.
We covered the time at which a captured variable is “snapshot.”
We briefly discussed the difference between “unowned
” and “weak
.”
SAMPLE PLAYGROUND
var externalContextVariable: Int = 5 // These are errors //let closure = {[inout externalContextVariable] () -> Void in //let closure = {[var externalContextVariable] () -> Void in let closure = {[externalContextVariable] () -> Void in // This is an error. // externalContextVariable = 6 print("externalContextVariable is \(externalContextVariable)") } closure() let closure2 = { externalContextVariable = 6 print("externalContextVariable is \(externalContextVariable)") } closure2() class SomeClass { var myString: String = "" var myInt: Int = 0 func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void { self.myString = stringValue self.myInt = intValue return {print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")} } } externalContextVariable = 10 var oneInstanceOfSomeClass: SomeClass! = SomeClass() var oneInstanceOfAClassBoundClosure: () -> Void = oneInstanceOfSomeClass.spewOutAClosure(intValue: 1000, stringValue: "One Thousand") oneInstanceOfAClassBoundClosure() oneInstanceOfSomeClass.myInt = 20 oneInstanceOfSomeClass.myString = "Twenty" oneInstanceOfAClassBoundClosure() class SomeClassWeakOverride: SomeClass { override func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void { self.myString = stringValue self.myInt = intValue return {[weak self] in if nil != self { print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self!.myInt)\nself.someString is \"\(self!.myString)\"") } else { print("self is nil!") } } } } class SomeClassUnownedOverride: SomeClass { override func spewOutAClosure(intValue: Int, stringValue: String) -> () -> Void { self.myString = stringValue self.myInt = intValue return {[unowned self] in print("externalContextVariable is \(externalContextVariable)\nself.myInt is \(self.myInt)\nself.someString is \"\(self.myString)\"")} } } var oneWeakInstanceOfSomeClass: SomeClassWeakOverride! = SomeClassWeakOverride() oneInstanceOfAClassBoundClosure = oneWeakInstanceOfSomeClass.spewOutAClosure(intValue: 900, stringValue: "Nine Hundred") oneInstanceOfAClassBoundClosure() oneInstanceOfSomeClass = SomeClass() oneInstanceOfAClassBoundClosure = oneInstanceOfSomeClass.spewOutAClosure(intValue: 10, stringValue: "Ten") oneInstanceOfAClassBoundClosure() oneInstanceOfSomeClass.myInt = 15 oneInstanceOfSomeClass.myString = "Fifteen" oneInstanceOfSomeClass = nil oneInstanceOfAClassBoundClosure() print("\n") var anotherInstanceOfSomeClass: SomeClassWeakOverride! = SomeClassWeakOverride() var anotherInstanceOfAClassBoundClosure = anotherInstanceOfSomeClass.spewOutAClosure(intValue: 10, stringValue: "Ten") anotherInstanceOfAClassBoundClosure() anotherInstanceOfSomeClass.myInt = 15 anotherInstanceOfSomeClass.myString = "Fifteen" anotherInstanceOfAClassBoundClosure() anotherInstanceOfSomeClass = nil anotherInstanceOfAClassBoundClosure() print("\n") var contextCheck: String = "Declaration" // This is the declaration phase of the closure. It is declared, but not instantiated. func generateAClosureForMe() -> () -> String { return {[contextCheck] in return contextCheck} } contextCheck = "Instantiation" // This is the instantiation phase. We ask the factory function to give us an instance of the closure. let myClosure = generateAClosureForMe() contextCheck = "Execution" // This is the execution phase. We run the closure here. print("The state was captured during the " + myClosure() + " phase.") contextCheck = "Declaration" // This is the declaration phase of the closure. It is declared, but not instantiated. func generateAClosureForMe2() -> () -> String { return {return contextCheck} } contextCheck = "Instantiation" // This is the instantiation phase. We ask the factory function to give us an instance of the closure. let myClosure2 = generateAClosureForMe2() contextCheck = "Execution" // This is the execution phase. We run the closure here. print("The state was captured during the " + myClosure2() + " phase.")