[Proposal] Allow using optional binding to upgrade self from a weak to strong reference


(E. Maloney) #1

Hello,

This is an update to the "Allow upgrading weak self to strong self by assignment" proposal I sent earlier. Rewritten to be explicitly limited to optional binding, thanks to some feedback from plx.

Proposal link here:

https://gist.github.com/emaloney/4bfcb21aaced15af8884

The text follows below.

E.

···

---

Allow using optional binding to upgrade self from a weak to strong reference

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 have the closure capture self weakly to avoid creating 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 starts the operation and provides a closure to be executed upon completion.

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 after 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, the 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 finishes, strongSelf goes away, once again making the view controller represented by self eligible for deallocation if no other references are held.

<https://gist.github.com/emaloney/4bfcb21aaced15af8884#the-problem>The Problem

The only available mechanism for upgrading a weak self to a strong reference requires the creation of a self-like variable with an arbitrary name—in the example above, strongSelf.

Because there is no compiler-level mechanism for enforcing a consistent name across an entire codebase, in some instances strongSelf may be ss or it may be s or it may be a random sequence of characters that captures the developer’s mood of the moment.

This lack of consistency adds noise to the codebase, and makes code harder to reason about, especially in cases where the strong reference is held by a variable with a name more cryptic than strongSelf.

Being able to upgrade self from a weak reference to a strong reference while retaining the name self would be ideal, and it would be consistent with the existing Swift convention of optional binding that reuses the name of the optional variable, eg.:

// foo is an optional here
if let foo = foo {
    // foo is non-optional here;
    // the optional foo is masked within this scope
}
// foo is once again an optional here
<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 optional binding.

In any scope where self is a weak reference, the compiler will accept an if or guard statement containing an optional binding that upgrades self to a strong reference.

This would allow self to keep its meaningful name instead of being renamed to something arbitrary.

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)
        }
    }
}
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#behavior>Behavior

Regardless of which notation is used for this feature, the behavior is the same:

Once bound, the strong self follows the same scoping rules as any other optionally-bound 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 will cause a compiler error.

Binding of self may only be used with let; attempting to bind 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>
[Proposal] Allow upgrading weak self to strong self by assignment <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160215/010691.html>


(Kurt Werle) #2

...

With this feature, the code above could be rewritten as:

networkRequest.fetchData() { [weak self] result in
    guard let self = self else { return }

If you're going to have boilerplate like that, then the following seems

like it accomplishes the same thing [in this specific case] without
changing anything:

networkRequest.fetchData() { [unowned self] result in

    guard self != nil else { return }

Kurt

···

On Fri, Feb 19, 2016 at 9:04 PM, Evan Maloney <emaloney@gilt.com> wrote:
--
kurt@CircleW.org
http://www.CircleW.org/kurt/


(Shawn Erickson) #3

I like this proposal. It allows the most freedom in how to deal with now
nil captured weak references and does it in a simple language consistent
way.

-Shawn

···

On Fri, Feb 19, 2016 at 9:04 PM Evan Maloney <emaloney@gilt.com> wrote:

Hello,

This is an update to the "Allow upgrading weak self to strong self by
assignment" proposal I sent earlier. Rewritten to be explicitly limited to
optional binding, thanks to some feedback from plx.

Proposal link here:

https://gist.github.com/emaloney/4bfcb21aaced15af8884

The text follows below.

E.

---

Allow using optional binding to upgrade self from a weak to strong
reference

   - 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 have
the closure capture self weakly to avoid creating 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
starts the operation and provides a closure to be executed upon completion.

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 after 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, the 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 finishes, strongSelf goes away, once again making the
view controller represented by self eligible for deallocation if no other
references are held.
<https://gist.github.com/emaloney/4bfcb21aaced15af8884#the-problem>The
Problem

The only available mechanism for upgrading a weak self to a strong
reference requires the creation of a self-like variable with an arbitrary
name—in the example above, strongSelf.

Because there is no compiler-level mechanism for enforcing a consistent
name across an entire codebase, in some instances strongSelf may be ss or
it may be s or it may be a random sequence of characters that captures
the developer’s mood of the moment.

This lack of consistency adds noise to the codebase, and makes code harder
to reason about, especially in cases where the strong reference is held by
a variable with a name more cryptic than strongSelf.

Being able to upgrade self from a weak reference to a strong reference
while retaining the name self would be ideal, and it would be consistent
with the existing Swift convention of optional binding that reuses the name
of the optional variable, eg.:

// foo is an optional hereif let foo = foo {
    // foo is non-optional here;
    // the optional foo is masked within this scope
}// foo is once again an optional here

<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 optional binding.

In any scope where self is a weak reference, the compiler will accept an
if or guard statement containing an optional binding that upgrades self to
a strong reference.

This would allow self to keep its meaningful name instead of being
renamed to something arbitrary.

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)
        }
    }
}

<https://gist.github.com/emaloney/4bfcb21aaced15af8884#behavior>Behavior

Regardless of which notation is used for this feature, the behavior is the
same:

   -

   Once bound, the strong self follows the same scoping rules as any
   other optionally-bound 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 will cause a compiler error.
   -

   Binding of self may only be used with let; attempting to bind 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>
   - [Proposal] Allow upgrading weak self to strong self by assignment
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160215/010691.html>


(Shawn Erickson) #4

True, however only if unowned works in terms of the desired object life
time / "ownership" for self. It isn't always sufficient so weak is needed
to avoid the retain cycle while safely supporting self becoming nil (e.g.
allowing the object referenced by self to go away while the block may still
be outstanding). The proposal outlines at least one example of that need
and is written assuming you need to use weak.

···

On Fri, Feb 19, 2016 at 10:15 PM Kurt Werle <kurt@circlew.org> wrote:

On Fri, Feb 19, 2016 at 9:04 PM, Evan Maloney <emaloney@gilt.com> wrote:

...

With this feature, the code above could be rewritten as:

networkRequest.fetchData() { [weak self] result in
    guard let self = self else { return }

If you're going to have boilerplate like that, then the following seems

like it accomplishes the same thing [in this specific case] without
changing anything:

networkRequest.fetchData() { [unowned self] result in

    guard self != nil else { return }

Kurt
--
kurt@CircleW.org
http://www.CircleW.org/kurt/


(Kurt Werle) #5

Arg! My mistake - I did not realize you can't test an unowned to be nil
like you can an implicitly unwrapped variable.

···

On Fri, Feb 19, 2016 at 11:03 PM, Shawn Erickson <shawnce@gmail.com> wrote:

True, however only if unowned works in terms of the desired object life
time / "ownership" for self. It isn't always sufficient so weak is needed
to avoid the retain cycle while safely supporting self becoming nil (e.g.
allowing the object referenced by self to go away while the block may still
be outstanding). The proposal outlines at least one example of that need
and is written assuming you need to use weak.

On Fri, Feb 19, 2016 at 10:15 PM Kurt Werle <kurt@circlew.org> wrote:

On Fri, Feb 19, 2016 at 9:04 PM, Evan Maloney <emaloney@gilt.com> wrote:

...

With this feature, the code above could be rewritten as:

networkRequest.fetchData() { [weak self] result in
    guard let self = self else { return }

If you're going to have boilerplate like that, then the following seems

like it accomplishes the same thing [in this specific case] without
changing anything:

networkRequest.fetchData() { [unowned self] result in

    guard self != nil else { return }

Kurt
--
kurt@CircleW.org
http://www.CircleW.org/kurt/

--
kurt@CircleW.org
http://www.CircleW.org/kurt/


(Taras Zakharko) #6

Even if that would be possible, you’d need to ensure that the RC of the object is not reduced to zero between your check and the end of the closure (that was the criticism with the first version of Evan’s proposal). I also read your comment sin the other thread and they seem quite focused on the unowned self case. I am not sure that its that frequent. When you sue closures for callbacks and/or signals, using weak self makes more sense to me. Another problem I have with the alternative proposal (guard self in the capture list) is that it does not offer an obvious way to remove the closure once the object leaves the scope. Imagine that I am using closures for some sort of observing or notification mechanism. There can potentially be many short-lived objects who receive notifications from a single source. With Evan’s proposal, I can do something like this:

guard self = self else { remove the current closure from the notification list; return }

I don’t see an obvious way to accomplish this in the alternative proposal.

Best,

Taras

···

On 20 Feb 2016, at 08:52, Kurt Werle <kurt@circlew.org> wrote:

Arg! My mistake - I did not realize you can't test an unowned to be nil like you can an implicitly unwrapped variable.

On Fri, Feb 19, 2016 at 11:03 PM, Shawn Erickson <shawnce@gmail.com <mailto:shawnce@gmail.com>> wrote:
True, however only if unowned works in terms of the desired object life time / "ownership" for self. It isn't always sufficient so weak is needed to avoid the retain cycle while safely supporting self becoming nil (e.g. allowing the object referenced by self to go away while the block may still be outstanding). The proposal outlines at least one example of that need and is written assuming you need to use weak.

On Fri, Feb 19, 2016 at 10:15 PM Kurt Werle <kurt@circlew.org <mailto:kurt@circlew.org>> wrote:
On Fri, Feb 19, 2016 at 9:04 PM, Evan Maloney <emaloney@gilt.com <mailto:emaloney@gilt.com>> wrote:

...
With this feature, the code above could be rewritten as:

networkRequest.fetchData() { [weak self] result in
    guard let self = self else { return }
If you're going to have boilerplate like that, then the following seems like it accomplishes the same thing [in this specific case] without changing anything:

networkRequest.fetchData() { [unowned self] result in
    guard self != nil else { return }

Kurt
--
kurt@CircleW.org
http://www.CircleW.org/kurt/ <http://www.circlew.org/kurt/>

--
kurt@CircleW.org
http://www.CircleW.org/kurt/ <http://www.circlew.org/kurt/>