cc @Joe_Groff, @lukasa - From my own experience, canceling a task from deinit
is easy: we have plenty of tools for that. What is dearly missing is the absolute and total confidence that there is no racy condition, unfortunate oversight, or any other bug, that could trigger the completion block to run despite task cancellation. Even seasoned developers who know an unowned reference is the best candidate prefer using a weak one, just because of this nagging doubt. The general description of cancellation as a cooperative and "best effort" feature makes nothing to lift this doubt, on the contrary.
// The ideal world
self.cancellable = publisher.sink { [unowed self] in
...
}
// The pragmatic world
self.cancellable = publisher.sink { [weak self] in
guard let self = self else { return }
...
}
If Swift concurrency can address this doubt with a clear commitment, written somewhere in the documentation, that acknowledges that people are full of doubts and soothes their questions, then that would be great for unowned
. On the first blog post that tells a story of a crash due to a deallocated object dereference, developers will turn to weak
again without any regret.
Below is an example of a conversation that is totally reasonable, but ruins confidence anyway:
Dev: My app crashes (see sample code that involves an unowned ref)!
Support: Everything is behaving as expected because cancellation happens after the continuation was already scheduled on the actor's inner queue.
Dev: