[swift-evolution] [Draft] @selfsafe: a new way to avoid reference cycles
Derrick Ho
wh1pch81n at gmail.com
Sun Feb 19 01:20:44 CST 2017
What wrong with [unowned self]
On Sat, Feb 18, 2017 at 11:01 PM Daniel Duan via swift-evolution <
swift-evolution at swift.org> wrote:
> This reminded me of an idea I had long time ago which will have a similar
> effect: add a way to disable implicit captures for closures. FWIW.
>
> > On Feb 18, 2017, at 5:24 PM, Matthew Johnson via swift-evolution <
> swift-evolution at swift.org> wrote:
> >
> > # `@selfsafe`: a new way to avoid reference cycles
> >
> > * Proposal: [SE-NNNN](NNNN-selfsafe.md)
> > * Authors: [Matthew Johnson](https://github.com/anandabits)
> > * Review Manager: TBD
> > * Status: **Awaiting review**
> >
> > ## Introduction
> >
> > This proposal introduces the `@selfsafe` function argument attribute
> which together with a `withWeakSelf` property on values of function type.
> Together these features enable library authors to create APIs can be
> statically verified to never extend the lifetime of the `self` a function
> they take may have captured. This is accomplished by allowing the library
> implementation convert the function to a nearly identical function that is
> guaraneteed to have a `weak` capture of `self` and be a no-op after `self`
> is released.
> >
> > Swift-evolution thread: []()
> >
> > ## Motivation
> >
> > Accidentally forgeting to use weak references is a common problem and
> can easily lead to reference cycles. Some APIs are best designed such that
> users *cannot* extend the lifetime of `self` by escaping a closure that
> happens to strongly capture `self`. For example,
> `UIControl.addTarget(_:action:for:) does not retain the target, thereby
> preventing users from making the mistake of using a closure with an
> accidental strong reference. We can do something similar in Swift:
> >
> > ```swift
> > // in the library:
> > func addTarget<T: AnyObject>(_ target: T, action: T -> Int -> Void) {
> > // store a weak reference to the target
> > // when the action is fired call ref.map{ action($0)(42) }
> > }
> >
> > // in user code:
> > class C {
> > init() {
> > addTarget(self, action: C.takesInt)
> > }
> >
> > func takesInt(_ i: Int) {}
> > }
> > ```
> >
> > Both the library and the caller have to deal with a lot of details and
> boilerplate that we would prefer to avoid. The natural design in Swift
> would be to simply take an action function. Unfortunately if we do that we
> run into a problem:
> >
> > ```swift
> > // in the library
> > func addAction(_ f: Int -> Void) {
> > // store a strong ref to f, which might include a strong ref to a
> captured self
> > // later when the action is fired call f(42)
> > }
> >
> > // in user code
> > class C {
> > init() {
> > addAction(takesInt)
> > // oops! should have been: addAction{ [weak self]
> self?.takesInt($0) }
> > }
> >
> > func takesInt(_ i: Int) {}
> > }
> > ```
> >
> > Here the syntax is much nicer, but unfortunately we have unintentionally
> extended the lifetime of `self`. The burden of ensuring `self` is not
> captured or captured weakly falls on users of the library.
> >
> > It would very nice if it were possible to design an API that has weak
> capture semantics while still acheiving the more concise and Swifty syntax.
> >
> > ## Proposed Solution
> >
> > This proposal introduces a read-only property on all function types:
> `withWeakSelf` as well as a `@selfsafe` function argument annotation.
> (This name for the annotation is a strawman - I would love to hear better
> ideas)
> >
> > ### `withWeakSelf`
> >
> > `withWeakSelf` can be imagined as a property declared like the following:
> >
> > ```swift
> > extension T -> Void {
> > var withWeakSelf: T -> Void { return // compiler magic }
> > }
> > extension T -> U {
> > var withWeakSelf: T -> U? { return // compiler magic }
> > }
> > extension T -> U? {
> > var withWeakSelf: T -> U? { return // compiler magic }
> > }
> > ```
> >
> > It returns a closure that is identical to itself in every respect except
> four:
> > 1. If the context includes a strong or unowned `self` capture, that is
> converted to a weak capture.
> > 2. If the function returns a non-`Void`, non-`Optional` type the return
> type is wrapped in an `Optional`.
> > 3. The function returned by `withWeakSelf` is a no-op after `self` has
> been released. If the return type is non-`Void`, the function returns
> `nil` after `self` has been released.
> > 4. Any addtional non-`self` context is released as soon as possible
> after `self` is released.
> >
> > From these rules, it follows that `withWeakSelf` can be a no-op unless
> `self` is captured strong or unowned or the return type is non-`Void` and
> non-`Optional`.
> >
> > Important note: any additional context the closure requies beyond `self`
> continues to be captured as it was originally.
> >
> > ### `@selfsafe`
> >
> > When a function argument is annotated with `@selfsafe` two rules are in
> effect for the implementation:
> > 1. The argument *is not* allowed to escape the function.
> > 2. The closure returend by `withWeakSelf` *is* allowed to escape the
> function.
> >
> > There should also be a warning if `argument.withWeakSelf` *does not*
> escape the function in any code paths (because the `@selfsafe` annotation
> is meaningless if it does not escape).
> >
> > This allows a library to do something like the following (a simplified
> version of a hypothetical future method on `UIControl`):
> >
> > ```swift
> > class UIControl {
> >
> > var action: () -> Void
> >
> > func setAction(_ action: @selfsafe () -> Void) {
> > self.action = action.withWeakSelf
> > }
> > }
> > ```
> >
> > In the previous example the function declares the argument with the
> `@selfsafe` annotation. The compiler verifies that the `action` argument
> does not escape `setAction` and that `action.withWeakSelf` *does* escape.
> Because these verifications guarantee that the lifetime of `self` cannot be
> exteneded by calling `setAction`, the compiler can allow users to omit
> `self` if this function is called with a closure.
> >
> > ```swift
> > class UIViewController {
> > func viewDidLoad() {
> > // misc setup
> > myControl.setAction(doSomething) // great! no problem creating a
> reference cycle.
> > myOtherControl.setAction {
> > // `self.doSomethingElse()` is not required.
> > // The compiler knows we cannot create a reference cycle here.
> > doSomethingElse()
> > }
> > }
> > func doSomething() {}
> > func doSomethingElse() {}
> > }
> > ```
> >
> > ## Detailed design
> >
> > TBD - If anyone can think of additional details that need to be spelled
> out please provide feedback in this thread.
> >
> > ## Source compatibility
> >
> > No effect. This is a purely additive change.
> >
> > ## Effect on ABI stability
> >
> > No effect. This is a purely additive change.
> >
> > ## Effect on API resilience
> >
> > No effect. This is a purely additive change.
> >
> >
> > ## Alternatives considered
> >
> > I considered various ways of exposing the entire captured context or the
> captured `self` reference directly as properties of function types, along
> with access to an unbound version of the function. This turns out to be
> far more complex than necessary to solve the problem this proposal aims to
> solve and doesn't enable any interesting use cases I have thought of.
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution at swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170219/6d4dcc16/attachment.html>
More information about the swift-evolution
mailing list