I have a Combine publisher-returning functions, and I wanted to provide an overload with a callback. My understanding that this is a common way to achieve this:
func loadImage() -> AnyPublisher<UIImage, Error> {
//...
}
func loadImage(completion: @escaping (UIImage) -> Void) {
var c: AnyCancellable? = nil
c = loadImage().sink(receiveCompletion: { _ in
c = nil
}, receiveValue: {
completion($0)
})
}
The closure temporarily retains a reference to the AnyCancellable until it completes.
This however causes a warning:
var c: AnyCancellable? = nil // Variable 'c' was written to, but never read
Is there a clean way to avoid this without adding unnecessary read statements?
Normally, you can just do _ = c, but there's also a problem that you need to ensure that c is still alive by the completion event.
If you're not reading from c, the compiler can assume that c isn't being used, and optimize it out. That'd be bad since you're retaining c to maintain the publisher subscription.
Use withExtendedLifetime on the last access to ensure that c is still alive by then. That last access should be in the completion block.
Could be either:
c = withExtendedLifetime(c) { nil }
// or
withExtendedLifetime(c) {}
c = nil
Thanks. I've never encountered withExtendedLifetime and still don't fully get this usage (i.e. shouldn't the entire thing be inside its closure?)
But, can you clarify - is _ = c enough as the last statement, or would the compiler still optimize it away and withExtendedLifetime the recommended approach?
The deal with withExtendedLifetime is that, x is guaranteed to be alive until the body completes its execution, even if body does not use x at all.
Now, what we want is that c is alive until completion is called. So we want to have withExtendedLifetime(c) { ... }, but we also want to mutate c to nil.
Since the x parameter is read-only, setting nil inside the body will violate the exclusivity rule:
withExtendedLifetime(c) {
// Don't do this.
c = nil
}
I don't think this is an exclusivity violation. withExtendedLifetime doesn't use inout, so passing c to it doesn't create a long-term access that the access in the closure could conflict with.