I may be too late for Swift 3, but I am planning to propose changes to the
default behavior for closures capturing object references. The introduction of
Swift Playgrounds has raised the importance of simplifying the coding of
leak-free, crash-free closures. New developers should not have to understand
closure memory management to start writing useful and correct code.
The topic of the closure weak/strong dance has been discussed on this list
before. This proposal differs from previous proposals in that it will eliminate
the dance altogether by default. I am very interested in hearing others’
opinions as to whether the benefits outweigh the costs of various options.
I have found that Swift’s capture lists and rules are a bit of a mystery to
many experienced developers, even though Swift’s closure capture rules are very
similar to those of Objective-C. Capture lists are probably thought of as
opaque incantations by many new Swift developers if they are aware of them at
all. Capture lists should, ideally, not be needed for sensible and safe default
behavior.
This discussion is iOS / OS X centric and uses terms from those domains, but
these issues should be applicable to almost any codebase that uses closures
capturing object references.
Capture lists are required by the fact that object references are captured as
`strong` by default, often leading to strong reference cycles and memory leaks.
Use of ‘unowned’
————————
Many examples of using closures capture self as `unowned`. Often, this pattern
does not scale well beyond simple examples. iOS and MacOS applications with
dynamic UIs, for example, switch between numerous views and view controllers.
These objects are dereferenced and reclaimed when they are no longer needed.
Closures capturing these objects as `unowned` crash when the references are
accessed.
Unfortunately, ‘unowned’ captures are tempting because they eliminate `guard`
and `if let` constructs and avoid littering code with optional unwrapping. They
are also slightly more performant, but this difference is probably negligible
in most application code.
Capturing escaping object references as `unowned` is only safe when object
lifetimes are perfectly understood. In complex systems, it is difficult to
predict execution order. Even if object lifetimes are perfectly understood when
code is originally written, seemingly innocuous changes to complex systems can
negate original assumptions.
For these reasons, I believe that capturing object references as `unowned`
should be considered an advanced optimization technique.
I now routinely create closures that capture `self` and other object references
as ‘weak’ even if I think that I feel that `unowned ` would be safe. This may
not be the absolutely most performant solution, but it is straightforward and
robust.
The core proposal:
——————
Closures capturing object references should automatically capture all object
references as weak.
The closure defined in:
```
class ClosureOwner2 {
var aClosure: (() -> Void)?
func aMethod() {
aClosure = { [weak self] in
self?.someOtherMethod()
}
}
func someOtherMethod(){}
}
```
would normally be written as:
```
aClosure = {
self?.someOtherMethod()
}
```
Closures that can be optimized to safely capture object references as `unowned`
can use the current syntax.
Swift 2 closure without explicit capture lists for object references will not
compile.
Capturing strong object references can be very useful in certain circumstances
and have a straightforward syntax:
```
aClosure = { [strong self] in
self.someOtherMethod()
}
```
Alternatives / Modifications / Improvements(?):
—————————————————————
1) Closures with object references could be simplified further by implicitly
including ‘let’ guards for all object references:
aClosure = {
// This is included implicitly at the top of the closure:
// guard let strongSelf = self else { return }
/*strongSelf*/ self.someOtherMethod()
print( “This will not appear when self is nil.” )
… other uses of strongSelf…
}
This would have the effect of making the execution of the closure dependent
upon the successful unwrapping of all of its object references. Object
references that are not required to unwrap successfully can be captured as
`weak’ if desired.
2) Some of the magic in #1 could be eliminated by introducing a new capture
type: ‘required’ to specify ‘weak guarded’ captures, allowing the example
closure to be written:
```
aClosure = { [required self] in
self.someOtherMethod()
print( “This will not appear when self is nil.” )
print( “This is not the default behavior and the reason for this is
clearly stated.” )
}
```
This reduces the amount of code required, but may increase the cognitive burden
over using the current syntax.
`required` is called `guard` in the “Simplified notation” proposal:
https://gist.github.com/emaloney/d34ac9b134ece7c60440
<https://gist.github.com/emaloney/d34ac9b134ece7c60440>
This proposal will differ from the “Simplified notation” proposal in that all
object references would be captured as `weak` by default in all closures.
Summary
—————-
Closures with objects references occur frequently in Swift code and the use of
closures is probably only going to increase. Current capture list rules are not
developer friendly and force developers to deal with subtle asynchronous memory
management issues. This increases the cognitive burden particularly for new
developers.
Closures should be safe and straightforward by default.
Enhancement #1 without #2 would probably be the easiest option for new
developers and result the smallest code size. The question is: is it too
“magical” and are the changes in execution behavior going to be significant in
practice?
The rules for closure object references with enhancement #1 can be stated
succinctly:
By default:
Closures do not affect reference counts of the objects that they reference.
All object references used within a closure must unwrap successfully for the
closure to execute.
I believe that these are safe, sensible and understandable rules that will
eliminate the need for capture lists for many closures. What do you think?
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution