The duration of the invocation of makeState is (mostly) unrelated to the lifetime of the Combine stream (except that the Combine stream 'kicks off' the invocation of makeState, so the stream must last at least long enough to do so). E.g., if we wrote:
Publishers.CombineLatest(firstPublisher, secondPublisher)
.map { [unowned self] in (self, $0.0, $0.1) }
.map {
let controller = LogicController()
controller.makeState(using: $0.0, first: $0.1, second: $0.2)
}
.assign(to: \.state, on: self)
.store(in: &tokens)
then it should be apparent that controller does not live on past each invocation of the closure passed to the second map (i.e., each time firstPublisher or secondPublisher produce a value).
When either publisher produces a value, Combine will (eventually) invoke the closure provided to map. We then allocate a LogicController and store a strong reference in controller. Then, we invoke controller.makeState, which forms an additional strong reference to the same LogicController, which is passed to the method as self. Then, makeState returns, and self is released. Then, the map closure returns, and controller is released. Finally, LogicController can be deallocated.
Looking back at the version using unowned self, the analysis is similar, except that there's no 'outer' strong reference:
Publishers.CombineLatest(firstPublisher, secondPublisher)
.map { [unowned self] in (self, $0.0, $0.1) }
.map { [unowned self] in self.makeState(using: $0.0, first: $0.1, second: $0.2) }
.assign(to: \.state, on: self)
.store(in: &tokens)
When either publisher produces a value, Combine eventually executes the closure passed to map. To execute self.makeState, we check whether self has been deallocated (and crash if so). Otherwise, we form a new strong reference to self and pass this reference to makeState.
During the invocation of makeState, self cannot be deallocated since there is an extant strong reference to self. Eventually, makeState returns, and self is released. At this point, there may or may not be strong references to self outstanding, but the Combine stream is not holding onto any.