Environment:
Xcode 11.4.1
Xcode 11.5 beta 2
iOS 13.4.1
iOS 13.5 (simulator)
macOS 10.15.4
Sample code
You can find example playground on github
Description
Subscriber receives new values after subscription to Publishers.Delay publisher has been cancelled.
- Create pipeline with Delay publisher.
- Wait for upstream to produce value.
- Cancel subscription before subscriber receives delayed value event.
Playground example:
import Cocoa
import Combine
import PlaygroundSupport
let page = PlaygroundPage.current
page.needsIndefiniteExecution = true
let delayedTimer = Timer.publish(every: 1.4, on: .main, in: .default).autoconnect() // or Just(Date())
.print("timer")
.delay(for: .seconds(5), scheduler: RunLoop.main)
.print("delay")
.eraseToAnyPublisher()
var sinkCancellable: AnyCancellable?
sinkCancellable = delayedTimer
.sink {
print("value \($0)")
}
DispatchQueue.main
.asyncAfter(deadline: .now() + .seconds(3)) {
sinkCancellable = nil
print("---- cancel ----")
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(15)) {
print("Playground Finished")
page.finishExecution()
}
Output
timer: receive subscription: (Timer)
delay: receive subscription: (SUBSCRIPTION_TYPE)
delay: request unlimited
timer: request unlimited
timer: receive value: (2020-05-10 09:07:09 +0000)
timer: receive value: (2020-05-10 09:07:11 +0000)
delay: receive cancel
timer: receive cancel
---- cancel ----
delay: receive value: (2020-05-10 09:07:09 +0000)
value 2020-05-10 09:07:09 +0000
delay: receive value: (2020-05-10 09:07:11 +0000)
value 2020-05-10 09:07:11 +0000
Playground Finished
As you can see provided closure is called after cancellable returned by sink
is deallocated.
Is this expected behavior?
This means we can't rely on lifecycle of returned cancellable when subscribing to a publisher.
For example, following code will crash if object is deallocated before delayed value is received.
protocol SomeDependency {
func someMethod() -> AnyPublisher<Any, Never>
}
class SomeObject {
private var cancellables = Set<AnyCancellable>()
private let dependency: SomeDependency
init(dependency: SomeDependency) {
self.dependency = dependency
}
func perform() {
dependency.someMethod()
.sink { [unowned self] value in
self.doStuff(with: value)
}
.store(in: &cancellables)
}
private func doStuff(with value: Any) {
// some logic
}
}