[swift-evolution] An upcoming proposal for simplifying leak free, safe closures.
Christopher Kornher
ckornher at me.com
Mon Jun 27 02:35:22 CDT 2016
> On Jun 26, 2016, at 4:25 PM, Russ Bishop <xenadu at gmail.com> wrote:
>
>
>> On Jun 26, 2016, at 12:10 PM, Christopher Kornher via swift-evolution <swift-evolution at swift.org> 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160627/da41ef59/attachment.html>
More information about the swift-evolution
mailing list