[swift-evolution] [Proposal] Allow upgrading weak self to strong self by assignment
Evan Maloney
emaloney at gilt.com
Fri Feb 19 12:38:52 CST 2016
Hello,
I've got a new proposal as an alternative to the one discussed in this thread: Simplified notation for avoiding the [weak self]/strongSelf dance with closures <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160208/009972.html>.
There seems to be almost universal opinion that the pain point described in that proposal proposal is real, however the community seems split on possible solutions.
I'm proposing this new alternative in the hopes that it will garner more support than a new guard capture type.
Link here:
https://gist.github.com/emaloney/4bfcb21aaced15af8884 <https://gist.github.com/emaloney/4bfcb21aaced15af8884>
The text of the proposal follows below.
Evan
---
Allow upgrading weak self to strong self by assignment
Proposal: TBD
Author: Evan Maloney <https://github.com/emaloney>
Status: Draft
Review manager: TBD
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#introduction>Introduction
When working with escaping Swift closures, it is a common pattern to capture self weakly to avoid an object reference cycle.
For example, let's say you have a view controller that displays the result of a network operation. When the view controller is placed onscreen, it executes the operation and provides a closure to be executed when the operation completes.
The fact that a network operation may be in-flight should not prevent user from navigating away from that view controller. Similarly, we don't want a pending network operation to prevent our view controller from being deallocated once it goes offscreen. In other words, we only care about the network operation while the view controller is alive; once the view controller has been deallocated, we can safely ignore the result of any network request it initiated.
To achieve this, your networking code might look something like:
networkRequest.fetchData() { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .Succeeded(let data):
strongSelf.processData(data)
case .Failed(let err):
strongSelf.handleError(err)
}
}
When it comes time to execute this closure, the guard statement effectively asks the question, "Is the view controller represented by self still alive?" If the answer is no, the guard forces a return and the rest of the closure does not execute.
If self is still alive, then the weakly-captured self will be non-nil and it will be converted into a strong reference held by strongSelf for the duration of the closure's execution.
When the closure is done executing, strongSelf goes away, once again making the view controller represented by selfeligible for deallocation if no other references are held.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#the-problem>The Problem
The [weak self]/strongSelf dance requires common boilerplate wherever it is used, and the fact that a self-like variable with an arbitrary name adds noise within the closure. The more strongSelf is needed within the closure, the more noise there is. Further, using a consistent name like strongSelf is by convention only; it can't be enforced by the compiler.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#proposed-solution>Proposed Solution
The proposed solution entails allowing self to be upgraded from a weak reference to a strong reference using assignment.
In any scope where self is a weak reference, the compiler will accept an assignment that upgrades self to a strong reference.
This would allow self to keep its meaningful name instead of being renamed to something arbitrary like strongSelf.
With this feature, the code above could be rewritten as:
networkRequest.fetchData() { [weak self] result in
guard let self = self else { return }
switch result {
case .Succeeded(let data):
self.processData(data)
case .Failed(let err):
self.handleError(err)
}
}
The following would also be legal:
networkRequest.fetchData() { [weak self] result in
if let self = self {
switch result {
case .Succeeded(let data):
self.processData(data)
case .Failed(let err):
self.handleError(err)
}
}
}
As would this:
networkRequest.fetchData() { [weak self] result in
guard self != nil else { return }
let self = self!
switch result {
case .Succeeded(let data):
self.processData(data)
case .Failed(let err):
self.handleError(err)
}
}
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#behavior>Behavior
Regardless of which notation is used for this feature, the behavior is the same:
The assignment of the strong self follows the same scoping rules as any other variable.
While the strong self is in scope, it masks the weak self variable. If the strong reference to self goes out of scope before the weak self reference does, the weak self will once again be visible to code.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#restrictions>Restrictions
To ensure safety, the compiler will enforce certain restrictions on the use of this feature:
Attempting to use this feature in a context where self is not a weak reference to an object will cause a compiler error.
Assignment of self may only be used with let; assigning self to a var is an error. (Because this feature only works with object references and not value types, this restriction does not affect the mutability of self.)
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#impact-on-existing-code>Impact on Existing Code
None, since this does not affect any existing constructs. Implementation of this proposal will not result in any code breakage.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#alternatives-considered>Alternatives Considered
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#status-quo>Status Quo
The primary alternative is to do nothing, requiring developers to add boilerplate guard code and handle upgrading the weak-to-strong references manually.
As stated above, this leads to needless boilerplate that can easily be factored out by the compiler. Also, the use of a self-like variable with an arbitrary name makes it more difficult to exhaustively find such uses in large projects.
Finally, the need to declare and use alternate names to capture values that already have existing names adds visual clutter to code and serves to obscure the code's original intent, making it harder to reason about.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#adding-a-new-guard-capture-type>Adding a new guard capture type
An alternate to this proposal involves adding a new capture type, called guard <https://gist.github.com/emaloney/d34ac9b134ece7c60440>, which would automatically handle upgrading self (and other references) from weak to strong.
Although the alternate proposal received a favorable response from the Swift Evolution mailing list, the community seemed split between the approach outlined in that proposal, and the one outlined here.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#citations>Citations
Variations on this proposal were discussed earlier in the following swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution> threads:
Wanted: syntactic sugar for [weak self] callbacks <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/008713.html>
Allowing guard let self = self else { … } for weakly captured self in a closure. <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009023.html>
[Draft Proposal] A simplified notation for avoiding the weak/strong dance with closure capture lists <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009241.html>
[Proposal Update 1] A simplified notation for avoiding the weak/strong dance with closure capture lists <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160208/009972.html>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160219/cf8d7850/attachment.html>
More information about the swift-evolution
mailing list