> On Feb 23, 2017, at 4:28 AM, Matthew Johnson <[email protected]> wrote:
>
>>
>> On Feb 22, 2017, at 7:13 PM, Anton Mironov <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>>>
>>> On Feb 23, 2017, at 02:19, Matthew Johnson <[email protected]
>>> <mailto:[email protected]>> wrote:
>>>
>>>>
>>>> On Feb 22, 2017, at 6:06 PM, Anton Mironov <[email protected]
>>>> <mailto:[email protected]>> wrote:
>>>>
>>>>
>>>>> On Feb 23, 2017, at 01:18, Matthew Johnson <[email protected]
>>>>> <mailto:[email protected]>> wrote:
>>>>>
>>>>>>
>>>>>> On Feb 22, 2017, at 5:06 PM, Anton Mironov <[email protected]
>>>>>> <mailto:[email protected]>> wrote:
>>>>>>
>>>>>> -1
>>>>>> I support improvements in this area but I do not think that adding
>>>>>> guarded closures will fix the case.
>>>>>> It raises multiple concerns:
>>>>>> - prepending ? to the closure declaration is as forgettable as `[weak
>>>>>> self]`
>>>>>
>>>>> No, this is why I included the `@guarded` parameter annotation. This
>>>>> allows an API to require its callers to use a guarded closure. Strong
>>>>> references would have to be explicit in the capture list.
>>>>>
>>>>>> - reactive programming often assumes chaining of operations. How guarded
>>>>>> closures affect next operations in the chain?
>>>>>
>>>>> Can you provide a concrete example of real world code you wrote manually?
>>>>> I will convert it to use guarded closures to show how it is affected.
>>>>
>>>> You can use the second piece of code I’ve provided before.
>>>
>>> How do `map` and `onUpdate` store the reference to the context? Is it
>>> weak? If so, what happens when the context is released? After you answer
>>> that I will be able to show how it would look under this proposal.
>>
>> Yes, they store a weak reference to the context.
>>
>> This code implies using primitive that has multiple update events (button
>> actions in this case) followed by a single completion event (can only be an
>> error of context deallocation in this case). I call it `Channel` (because
>> name `Stream` is already taken by `Foundation.Stream` aka `NSStream`).
>>
>> A release of context will lead to a release of closure and all operations it
>> depends on.
>>
>> `Channel.map`’s context does not exist anymore so closure will be released
>> and `Channel` returned from `map` will be immediately completed with failure.
>> This will also lead to a release of `Channel` returned by
>> `Channel.debounce()`. `Channel.debounce()` will lead to release of `Channel`
>> returned by `actions(forEvents: [.touchUpInside])`.
>>
>> `Channel.onUpdate`’s context does not exist anymore so closure will be
>> released.
>> This will also lead to a release of `Channel` returned by
>> `Channel.distinct()`.
>>
>> It might look complex but it is based on a simple idea: release all retained
>> resources if your context is gone.
>
> I’m very familiar with patterns like this but didn’t want to make any
> assumptions about how your code works. How does this code currently detect
> that the context has been released? Does it wait until an event is pushed
> through the channel and see that the context is now nil?
>
No, it does not wait. It releases all retained resources (closure and channels
it depends on) when context drains it’s `disposableBag`.
> If this system was using guarded closures the `map` and `onUpdate` methods
> would specify their function argument `@guarded` which would automatically
> make all captures guarded. You are not limited to a single context object,
> but if that is the need of your code you of course can capture a single
> object.
>
I did not think of multiple contexts before. Maybe I should. I thought that two
(or more) context you want to interact with are either:
- have a simple relation (child-parent or a regular ownership) so there is no
need for the weak reference for the second context
- are operating on unrelated `DispatchQueue`s so mutation of an internal state
of both contexts on the same queue may not exist
> The calling code would look like this:
>
> self.button.actions(forEvents: [.touchUpInside])
> .debounce(interval: 3.0)
> .map ?{
> return self.searchField.text
> }
> .distinct()
> .onUpdate ?{ (searchQuery) in
> self.performSearch(query: searchQuery)
> }
>
> In the implementation of the library where you used to store a weak reference
> to the context and a strong reference to the closure you would just store a
> strong reference to the guarded closure. The guarded closure itself manages
> the weak / strong dance. Where you used to check the weak reference to the
> context for nil to see if it is alive or not you would check the `isAlive`
> property on the closure reference to determine if the closure is still alive
> or not. When it is no longer alive you tear down exactly the same as you do
> today when you detect that the context reference is nil.
That is a point. Next check `isAlive` will be performed right before the next
call of the closure. This call could be performed in a minute or in an hour or
even never performed. Guarded closure will capture variables until then.
>
>>
>>>>
>>>>>
>>>>>> - the closure must exist until either the control deallocates (source of
>>>>>> actions) or self deallocates (destination of actions). Guarded closure
>>>>>> will not provide an expected behavior
>>>>>
>>>>> Yes they will. The guarded closure lives until the control releases it.
>>>>> But it becomes a no-op if any of the references captured with a guard are
>>>>> released before that happens. This is much like the behavior of the
>>>>> target / action pattern but generalized to support closures.
>>>>
>>>> I doubt that turning closure into no-op is a simple thing to do. It will
>>>> require having a registry of closures that depend on an instance. A
>>>> runtime will have to go through the registry and turn closures into no-op.
>>>> Or there is an another solution that I do not see.
>>>
>>> What I mean when I say no-op is that the code the user places in the
>>> closure would be prefixed by a `guard` clause with an early return. The
>>> no-op is when the guard clause is triggered, just as if you had written it
>>> manually.
>>
>> I think that this is an important part. Using no-op (or avoiding execution
>> of closure as I understand it) leads to another unwanted retain of variables
>> captured by the closure.
>
> There is no additional retain of the variables over a solution that captures
> the variables independently of the closure and passes them as arguments if
> they’re alive when called (like the context in your example). Where do you
> think you see an extra retain of the variables?
I meant that keeping the closure alive when a context is dead is an extra
retain of a captured variables.
Look, I’m not saying that extra care about retain cycles is bad. I’m just
saying that making an implicit weak reference does not solve the whole problem.
I think that adding guarded closures is not a step towards a complete solution.
>
>>
>>>
>>> I imagine the compiler might also place an additional check inside the
>>> `else` of the `guard` which would release any context it was still hanging
>>> on to as it would know that the context was no longer needed. Imagine this
>>> as if the compiler synthesized code had access to an optional strong
>>> reference to the context and set it to nil in the else clause. It would
>>> also be possible for this closure to expose an `isAlive: Bool` property
>>> that would check whether or not the context was still around. This is a
>>> bit of hand waving - I’m sure the real implementation would be more
>>> sophisticated. But I think that conveys the basic idea.
>>>
>>> Here is some pseudocode:
>>>
>>> let foo: Foo = Foo()
>>> let bar: Bar = Bar()
>>>
>>> // hand out references to the owner that controls the lifetime of foo and
>>> bar
>>>
>>> ?{
>>> // compiler synthesized code
>>> // this is a property on the closure object itself, not visible
>>> within the scope of the user code in the closure
>>> // it is used by libraries to detect when they can safely discard
>>> their reference to the closure because it has become a no-op
>>> // context is a super secret compiler reference to the context of
>>> the closure
>>> var isActive: Bool { return context != nil }
>>>
>>> // compiler synthesized code that prefixes the user code
>>> guard let foo = foo, let bar = bar else {
>>> context = nil
>>> return
>>> }
>>> // end compiler synthesized code
>>>
>>> // begin user code
>>> // do something with foo and bar
>>> }
>>>
>>>>
>>>>>
>>>>>> - managing lifecycle of nested guarded closures could be complex to
>>>>>> understand and implement into the language
>>>>>
>>>>> I’m glad you brought this up. I’ll give it some thought. If there does
>>>>> turn out to be complexity involved I wouldn’t have a problem prohibiting
>>>>> that.
>>>>>
>>>>>> - why would you consider using @escaping instead of @guarded?
>>>>>
>>>>> Because sometimes the right default for a function taking an escaping
>>>>> closure is a strong reference. I wouldn't want `DispatchQueue.async` to
>>>>> take a guarded closure. That API doesn’t contain any semantic content
>>>>> around *why* you dispatched async. It’s not a callback, but instead a
>>>>> way of moving work around.
>>>>>
>>>>>>
>>>>>> I personally prefer doing something like this:
>>>>>>
>>>>>> ```swift
>>>>>> self.button.onAction(forEvents: [.touchUpInside], context: self) {
>>>>>> (self, sender, event) in
>>>>>> self.performSearch(query: self.searchField.text)
>>>>>> }
>>>>>> ```
>>>>>>
>>>>>> or
>>>>>>
>>>>>> ```swift
>>>>>> self.button.actions(forEvents: [.touchUpInside])
>>>>>> .debounce(interval: 3.0)
>>>>>> .map(context: self) { (self, _) in
>>>>>> return self.searchField.text
>>>>>> }
>>>>>> .distinct()
>>>>>> .onUpdate(context: self) { (self, searchQuery) in
>>>>>> self.performSearch(query: searchQuery)
>>>>>> }
>>>>>> ```
>>>>>>
>>>>>> This code neither requires an addition of language features nor contains
>>>>>> retain cycles. All closures will be released as soon as source or
>>>>>> destination deallocates.
>>>>>
>>>>> This isn’t too bad but it does require manually threading the context.
>>>>> This is more work for both the library and the client than necessary. It
>>>>> also does not help users avoid an accidental strong reference in the
>>>>> closure. It nudges them not to by offering to thread the context but it
>>>>> doesn’t do anything to prevent it. You can still create a strong
>>>>> reference (event to self) without specifying it in the capture list.
>>>>
>>>> You are correct. This will code will not help to avoid accidental strong
>>>> reference. But it gives an opportunity to do things without explicit weak
>>>> references just as guarded closures do. It also adds an ability to avoid
>>>> an execution of pure (or just not bound to context) operations you depends
>>>> on. Deallocation of context will lead to cancellation of full chain of
>>>> operations and unsubscription from button event.
>>>>
>>>>> I think there is a place for a language solution here.
>>>>
>>>> The only language solution I expect is a static analyzer warning about
>>>> retain cycle (like in ObjC).
>>>>
>>>> I’m starting to think that my solution is similar to yours. I’ve done
>>>> these things with a library rather than with language support. I will
>>>> definitely take advantage of guarded self in closures as soon as the
>>>> proposal will be accepted. But I would prefer and suggest using my
>>>> solution for now.
>>>>
>>>>>
>>>>>>
>>>>>>> On Feb 22, 2017, at 22:57, Matthew Johnson via swift-evolution
>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>
>>>>>>> Hi David,
>>>>>>>
>>>>>>> I just shared a draft proposal to introduce guarded closures last week:
>>>>>>> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170213/032478.html
>>>>>>>
>>>>>>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170213/032478.html>.
>>>>>>> I think you would find it very interesting.
>>>>>>>
>>>>>>> I considered including a new capture list specifier `guard` in this
>>>>>>> proposal but decided against it. Guarded behavior requires prefixing
>>>>>>> the contents of the closure with a guard clause that returns
>>>>>>> immediately if the guard is tripped. This is a property of the closure
>>>>>>> as a whole, not of an individual capture. For that reason, I decided
>>>>>>> that allowing a `guard` specifier for an individual capture would be
>>>>>>> inappropriate.
>>>>>>>
>>>>>>> Instead, a guarded closure has a guarded by default capture behavior
>>>>>>> which can be overridden with `weak`, `unowned` or `strong` in the
>>>>>>> capture list. The thread on this proposal was relatively brief. I
>>>>>>> plan to open a PR soon after making a few minor modifications.
>>>>>>>
>>>>>>> Matthew
>>>>>>>
>>>>>>>> On Feb 22, 2017, at 2:48 PM, David Hedbor via swift-evolution
>>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>>
>>>>>>>> Hello,
>>>>>>>>
>>>>>>>> (apologies if this got sent twice - gmail and Apple mail seems to
>>>>>>>> confused as to what account the first mail was sent from)
>>>>>>>>
>>>>>>>> I’m new to this mailing list, but have read some archived messages,
>>>>>>>> and felt that this would be a reasonable subject to discuss. It’s
>>>>>>>> somewhat related to the recent posts about @selfsafae/@guarded but
>>>>>>>> distinctly different regardless.
>>>>>>>>
>>>>>>>>
>>>>>>>> Problem:
>>>>>>>>
>>>>>>>> It’s often desirable not to capture self in closures, but the syntax
>>>>>>>> for doing so adds significant boilerplate code for [weak self] or us
>>>>>>>> unsafe when used with [unowned self]. Typically you’d do something
>>>>>>>> like this:
>>>>>>>>
>>>>>>>> { [weak self] in self?.execute() }
>>>>>>>>
>>>>>>>> This is simple enough but often doesn’t work:
>>>>>>>>
>>>>>>>> { [weak self] in self?.boolean = self?.calculateBoolean() ]
>>>>>>>>
>>>>>>>> This fails because boolean is not an optional. This in turn leads to
>>>>>>>> code like this:
>>>>>>>>
>>>>>>>> { [weak self] in
>>>>>>>> guard let strongSelf = self else { return }
>>>>>>>> strongSelf.boolean = self.calculateBoolean() }
>>>>>>>>
>>>>>>>> And this is the boilerplate code. My suggestion is to add a syntax
>>>>>>>> that works the same as the third syntax, yet doesn’t require the
>>>>>>>> boilerplate code.
>>>>>>>>
>>>>>>>>
>>>>>>>> Solution:
>>>>>>>>
>>>>>>>> Instead of using unowned or weak, let’s use guard/guarded syntax:
>>>>>>>>
>>>>>>>>
>>>>>>>> { [guard self] in
>>>>>>>> self.isExecuted = self.onlyIfWeakSelfWasCaptured()
>>>>>>>> }
>>>>>>>>
>>>>>>>> In essence, guarded self is equivalent to a weak self, that’s captured
>>>>>>>> when the closure is executed. If it was already released at that
>>>>>>>> point, the closure is simply not executed. It’s equivalent to:
>>>>>>>>
>>>>>>>> { [weak self] in
>>>>>>>> guard let strongSelf = self else { return }
>>>>>>>> strongSelf.isExecuted = strongSelf.onlyIfWeakSelfWasCaptured()
>>>>>>>> }
>>>>>>>>
>>>>>>>> Except with a lot less boilerplate code, while not losing any clarify
>>>>>>>> in what it does.
>>>>>>>>
>>>>>>>> Impact / compatibility:
>>>>>>>>
>>>>>>>> This is simply additive syntax, and wouldn’t affect any existing code.
>>>>>>>> _______________________________________________
>>>>>>>> swift-evolution mailing list
>>>>>>>> [email protected] <mailto:[email protected]>
>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> [email protected] <mailto:[email protected]>
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution