Introducing a special syntax for blocks who capture all variables as weak. For example:
publisher.sink {? self.update(button) }
Will be equivalent to
publisher.sink { [weak self, weak button] in
guard let self = self else { return }
guard let button = button else { return }
self.update(button)
}
Motivation
Many of the UI related use of blocks requires the developer to add lots of boilerplate code to allow weak capturing of self and other variables used in the scope of the block when updating the UI to avoid retain cycles. It also doesn't make sense to strongly capture the UI only for updating it if its already dismissed.
By introducing the new syntax to weakly capture all variables in the scope we can also avoid bugs caused by the default behaviour of implicitly strong capturing all variables in the block scope.
This moves the focus away from deciding if strongly capturing a variable in this block will cause a retain cycle or not to letting the developer focus on whether this block must execute or is the block optional and should only be executed if all of its non optional capture list still exists.
Effect on ABI stability
The proposed solution maintains ABI stability since it introduces a new syntax that doesn't effect previous swift versions.
Discussion Summary
@Morten_Bek_Ditlevsen: how would optional chaining in the synthesized ‘guard let’ work? @eventomer: Guard statements will be added only for variables that were not optional when they were captured
@Morten_Bek_Ditlevsen: Could you write capture lists in this syntax? For instance if you actually need a single strong capture? @eventomer: I can think of reasons for both ways. lets hear more input regarding this
@Morten_Bek_Ditlevsen: Should a similar {! … } exist for capturing everything unowned? @eventomer: it does feel natural to have support for {! … } as well however personally i didn't use unowned yet in any context so i'm not sure what will be the value of having it but i'm open to it too if anyone else finds value in it
Another question regarding optionals. If ‘button’ in your example was optional would the closure only be run if the button exists?
In other words: how would optional chaining in the synthesized ‘guard let’ work?
I don’t think I have an opinion (yet) - I’m just brain storming.
I don’t know yet if I’m a fan of introducing this new syntax although I have been using weak self + guard let unwrapping a long time in the context of RxSwift observables subscribed to from the ui…
Should a similar {! … } exist for capturing everything unowned?
it does feel natural to have support for {! … } as well however personally i didn't use unowned yet in any context so i'm not sure what will be the value of having it but i'm open to it too if anyone else finds value in it
The proposed spelling is conflating optionality (?) with weak references (weak). Just because you make a strong reference optional doesn’t mean it becomes a weak reference. You need to explicitly tell the compiler – and readers of your code – that it’s a weak reference.
If the language did include this feature (and I’m skeptical that it should) it would need to be spelled in a way that makes it clear that the captures are weak, probably using the word weak somehow. That the captures are optional would then follow from that – not the other way around.
We need to convey both the weak references and the guards that bail out whenever a single one of the weak captures has turned nil. We're after a combo.
I pretty much like the proposal. It does only one thing, very useful, without much fuss. For all other cases, we'd have to use the existing capture syntax, which is pretty much ok.
You're right. But this pitch, which deals with the specific case "every references are weak and all the block implicitly returns nil if any one is nil", with a single sigil, is very new, and very simple.
All critics so far against implicit weak references turn around:
one usually should use strong references
or at least use unowned references
(1) ignores the occasions when we don't care about the side effects (typically UI code)
(2) ignores the fact that unowned references require a great deal of trust that the code won't run after captured values have been deinited. This trust is sometimes impossible to grant, especially in racy contexts. For example, I do not trust Combine. And I do not trust the async tooling we'll soon profit from, due to its cooperative cancellation handling.
I really think this pitch is new, useful, and simple.
As someone very new to swift, I like this spelling much better. It’s clear, helps with discoverability/learning, and seems to provide a standard look to possible future ways to set/change the defaults to automate boilerplate code.
Maybe we should go at it with a different approach. Instead of a blocks syntax change we can use attributes the same way we use the @escaping attribute.
So if we have a block which we know that we are going to keep as a strong reference we can add @weakCapturing and @guardCapturing attributes to indicate how its capture list should behave.
For example I would be able to declare the following function:
I was reworking a observed node graph today. Not having time to rewrite everything, i wrapped the handlers in captures to add unowned self. (It was nice getting hard crash to see where my deductions of ownership direction were wrong)
This syntax would have allowed me to change just the definitions and have the ownership or guard be automatic.