[swift-evolution] [Draft] @selfsafe: a new way to avoid reference cycles
Matthew Johnson
matthew at anandabits.com
Sun Feb 19 08:45:32 CST 2017
Sent from my iPad
On Feb 19, 2017, at 2:15 AM, Brent Royal-Gordon <brent at architechies.com> wrote:
>> On Feb 18, 2017, at 5:24 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>>
>> 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.
>
> Both of these mechanisms are weirdly ad hoc. They involve the callee assuming things about the caller that are not necessarily correct—in particular, that the caller's `self` is going to, directly or indirectly, hold a strong reference to the callee's `self`.
After I sent this draft last night I realized we can eliminate the `withWeakSelf` property on functions and just have the compiler perform the conversion when the argument is passed. This just requires restricting the `@safeself` annotation to `Void` and `Optional` returning functions. This will prevent unintended uses of that property and keep more of the implementation details hidden, so let's just focus on `@selfsafe`. (Also note: this name is a straw man, I would like to find something better)
Here's the logic that resulted in this idea:
1. Swift *already* acknowledges that it is far easier to create a reference cycle through captured strong references to `self` than any other way. This is why you have to explicitly say `self.` in escaping closures.
2. There are *already* APIs which are designed to capture an object weak and then call a method on it if it's still around when an event occurs. In fact this has been a trend in Apple's newer APIs. Users are able to learn the semantics of these APIs without a problem. In fact, users like the, because they solve a real problem by ensuring that3. object lifetime is not extended when using them.
3. Swift libraries don't tend to design APIs with weak callback semantics, probably because they are currently syntactically heavy for both users and libraries. This is true even when weak callback semantics would be beneficial for users and help prevent leaks (which can lead to crashes and other badly behaved apps).
4. There have been several ideas proposed to make weak capture easier to do on the call side but they haven't gone anywhere. The syntactic savings aren't that significant for callers and the burden is still on callers to get it right.
All of these facts indicate to me that it should be possible to design an API with weak callback semantics that does not impose a significant syntactic burden on callers. I am not as concerned about a possible syntactic burden on libraries, but clients should be able to pass `myMethod` or `{ myMethod(); myOtherMethod() }` and have it "just work". A small decoration like the `&` required for `inout` would be acceptable, but not much more than that.
>
> For instance, suppose you've read too many design pattern books, and you're using the Command Pattern:
>
> class DeleteRecordCommand: Command {
> let record: Record
>
> func execute(with viewController: RecordViewController) {
> let alert = UIAlertController(title: "Really delete?", preferredStyle: .alert)
>
> alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in
> self.record.delete()
> viewController.performSegue(withIdentifier: "Cancel", sender: self)
> })
> alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
>
> viewController.present(alert)
> }
> }
>
> Now, imagine that the `UIAlertAction` initializer used `@selfsafe`, hoping to prevent incorrect use of `self`. Well, that would be totally wrong in this case—it would weaken `self`, which *needs* to be strong, and leave `viewController` strong, when it's creating a retain cycle. `@selfsafe` didn't prevent a bug—it introduced another one, without any visible sign of it in the code.
You bring up a very important point. I was going to include a section on "what if users actually need a strong reference" and simply forgot to add that before I sent it out.
If users actually need a strong reference there are ways to handle that. First, if it is likely that a user might want self to be captured strong the API might choose to not use `@selfsafe`. If they *do* choose to use it the could also add an overload that does not use it and is disambiguated using some other means (base name or argument label). Finally, users can always add an extra closure wrapper {{ self.myMethod() }} to bypass the API's weak callback semantics. Here, `self` is captured by the inner closure rather than the the outer closure and the API only converts strong `self` references in the outer closure.
All of this aside, I think you make a great point that in the design I proposed it is possible for users to intend a strong reference and have it converted without any sign of that at the call site. What if we required a sigil at the call site for `selfsafe` like we do for `inout`? This would make it clear at the call site that the lifetime of `self` is not extended by the closure and it is only invoked if `self` is still around when an event occurs. I'm going to try to think of a good sigil to use for this.
>
> *The problem is not `self`.* The problem is that you need to pay attention to memory management in Swift. And that's a tradeoff that's baked into the language.
Yes, and it's also something that we strive to handle automatically where possible and make as easy as possible to do correctly. I am *very* careful about capturing strong references to `self` and still make an accidental mistake every once in a while.
I am very confident that enabling the class of weak callback APIs I have described is a good idea (regardless of how the end result is designed). This would be a powerful tool in our tool belts and help make correct resource lifetime management easier than it is today.
>
> --
> Brent Royal-Gordon
> Architechies
>
More information about the swift-evolution
mailing list