EDIT: I was wrong here
I guess that will be incorrect once this proposal is accepted as one would be able to send Trampoline
to another isolation domain regardless of its sendability.
What we need here is the above mentioned Isolated synchronous deinit, but there weren't any updates on that in past two years. So I guess it would be better to implement a workaround on your side than wait.
I think this can be solved by introduction another private object to interact with UIControl
and move the call to removeTarget
from deinit
into a method, so it will be isolated. This object should be held by the outer object (Trampoline
) and never leaked out. Something like
public extension UIControl {
@MainActor
func observe(event: UIControl.Event, _ block: @escaping @MainActor (UIControl) -> Void) -> Any {
return Trampoline(observee: self, event: event, block: block)
}
}
private final class Trampoline {
@MainActor
final class Inner {
private weak var trampoline: Trampoline?
private weak var observee: UIControl?
init(observee: UIControl, event: UIControl.Event, trampoline: Trampoline) {
self.trampoline = trampoline
self.observee = observee
observee.addTarget(self, action: #selector(Inner.bounce(sender:)), for: event)
}
@objc func bounce(sender: UIControl) {
trampoline?.block(sender)
}
func unsubscribe() {
observee?.removeTarget(self, action: nil, for: .allEvents)
}
}
private let block: @MainActor (UIControl) -> Void
private var inner: Inner!
@MainActor
init(observee: UIControl, event: UIControl.Event, block: @escaping @MainActor (UIControl) -> Void) {
self.block = block
inner = Inner(observee: observee, event: event, trampoline: self)
}
deinit {
Task { @MainActor [inner = inner!] in
inner.unsubscribe()
}
}
}
(Be careful, I haven't tested this)