> On Jun 26, 2016, at 4:25 PM, Russ Bishop <[email protected]> wrote:
>
>
>> On Jun 26, 2016, at 12:10 PM, Christopher Kornher via swift-evolution
>> <[email protected]> wrote:
>>
>> 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.
>
> The problem is that strong reference capture is probably the far more common
> case.
Strong reference capture has not been more common in carefully written code in
my experience. Swift is starting to be used for many different problem domains,
so your experience may be different. Any examples of real-world code would be
greatly appreciated.
Sometimes closures contain the only references to objects, but this is not
common in code that I have seen. It would be extremely rare for strong
references to be required for all the references in closure with multiple
references (the current default behavior). Long closures can reference many
objects and it is all too easy to leave a reference out of the capture list and
create a reference cycle. I believe that this pattern should be called-out (see
below).
Strong references are occasionally needed to ensure operations are performed
when objects would otherwise be reclaimed, but I have not seen many of these
cases. This pattern could be more common in other frameworks. I believe that
this pattern should be called-out (see below).
Multiple capture rules for closures can be supported if desired. The
```[required…``` capture qualifier in the original email is one way todo this.
The question mark could be used in a way analogous to `try?` to identify
closures using the proposed rules:
```let a:()->Void = {…}?```
or
``` let a:()->Void = ?{…}```
etc.
This would obviously add more complexity but would still be an improvement, I
believe.
> If you wanted to say that @noescape closures capture references strongly by
> default, while escaping closures capture them weakly by default that may be
> closer to reasonable for most situations but now you have magic behavior with
> an even greater cognitive overhead. (I wonder if it would be more common that
> @noescape captures strongly, escaping captures self weak by default but other
> objects strongly… of course that would have even worse cognitive overhead.)
I did consider treating self differently, but this leads to some very strange
cases when delegation, factories and other patterns are considered.
This email was implicitly referring to escaping uses of closures. The same
closure can be used as escaping or non-escaping. The Swift documentation states:
"Marking a closure with @noescape lets the compiler make more aggressive
optimizations because it knows more information about the closure’s lifespan."
@noescape is essentially a hint to the compiler. Optimizers would be free to
use strong or unowned references if they can determine that it is safe to do so
without altering behavior.
> No matter what, without a garbage collector you are stuck with sub-optimal
> solutions for fixing reference cycles. I could imagine a language feature
> that automatically detected trivial cycles (A -> B -> A) but anything more
> complex just becomes a form of garbage collection anyway.
Object networks are difficult enough with ARC without dozens of closures with
unnecessary strongly captured references. The new Xcode tools will be a huge
help with leaks, but they should not be required.
>
> I don’t think there is a way to square this circle. Either you have one
> “automagic” behavior that is wrong for some cases (whether strong or weak is
> the default), or you require everyone to spam their closures with explicit
> capture annotations even if there’s a default “capture everything strongly”
> variant (see C++ lambdas).
Yes, we are discussing tradeoffs. Writing correct code with closures will
always require care.I believe that is is better to have safe, leak free code by
default than not. I also believe that capturing strong references by default
can probably lead to at least as many unexpected behaviors as capturing weak
references. I don’t think that many developers would expect, for example, to
see zombie view controllers that have not been associated with an active view
hierarchy for weeks because a closure is holding on to a strong reference.
>
>>
>> Use of ‘unowned’
>> ————————
>>
>> 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.
>
> I agree and our team has adopted the rule that use of unowned is not allowed
> unless the declaration is private and there is profiler proof that it
> represents a performance problem, and if used warning comments must be placed
> in the code. Weak is almost never a performance problem and eliminates the
> risk of a crash, so it is highly preferable to unowned.
>
> I’d go so far as to say unowned should be removed; let the user use
> Unmanaged<T> if they need to capture an unowned reference. They’re entering
> expert territory anyway.
I use ‘unowned' regularly for back pointers but not closures. The documentation
should contain more warnings about the use of `unowned` at least.
>
>
>
>>
>> The core proposal:
>> ——————
>>
>> Closures capturing object references should automatically capture all object
>> references as weak.
>>
>
> This becomes a form of the Objective-C messages-to-nil-do-nothing problem
> where you don’t crash but your closure doesn’t do any work (or does half the
> work!) because the reference(s) are/become nil. It doesn’t save you from
> reasoning about object lifetime because it is just as easy for the closure to
> capture the last reference to an object or for the lifetime to differ from
> the closure lifetime. You’re just trading reference cycles for a different
> problem.
Yes, this is based upon the opinion that operations on objects that would
otherwise have been reclaimed very often can be ignored. If they cannot be
ignored, the reference must be captured strongly, but this is relatively rare
and should probably be called-out anyway, given the way that developers are
forced to automatically invoke the weak/strong dance when creating closures.
This suggestion is based upon my experience with thousands(?) of Swift closures
in iOS apps in the past two years.
>>
>>
>> 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:
>>
>
> This has been debated before. I support the idea of a “required” capture
> specifier but IIRC the core team was not supportive of the idea because it
> adds another layer of magic on the existing magic (object deallocated
> magically means your closure never executes).
The increase in complexity may not be worth the elimination of guard
statements. This proposal is different in that it retains a single capture
default for closures. The ‘required’ keyword does not change the way references
are captured. This may be a significant difference or not.
>
> Russ
>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution