This is Understanding Combine, written by Matt Neuburg. Corrections and suggestions are greatly appreciated (you can comment here). So are donations; please consider keeping me going by funding this work at http://www.paypal.me/mattneub. Or buy my books: the current (and final) editions are iOS 15 Programming Fundamentals with Swift and Programming iOS 14. Thank you!
The .assign
subscriber (Subscribers.Assign) is legal only with a publisher whose error type is Never (because it has no means of coping with a failure message):
func assign(to:on:) -> AnyCancellable
(I’ll deal later with the significance of the fact that the assign
method produces an object typed as AnyCancellable rather than Subscribers.Assign.)
The first parameter is a property, designated as a Swift keypath; the second parameter is an object. The outcome is that the incoming value will be assigned to that property of that object.
The example I gave earlier was of a UIImage coming down the pipeline and being assigned to an image view’s image
property:
.assign(to: \.image, on: self.iv)
It isn’t clear to me that the .assign
subscriber does anything you couldn’t do with the .sink
subscriber; after all, you could assign the image to an image view’s image
property in the .sink
function:
.sink { self.iv.image = $0 }
However, .assign
is a nice way of expressing the common case where the purpose of the pipeline is to use the value to set a property of some object.
On the other side of the coin, .assign
can dangerous: you can end up with a retain cycle. This occurs in the common situation where what you’re assigning to is a property of self
. The example I’ve used repeatedly is not a case in point:
URLSession.shared.dataTaskPublisher(for: url)
.map {$0.data}
.replaceError(with: Data())
.compactMap { UIImage(data:$0) }
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self.iv)
.store(in:&self.storage)
In the first place, self.iv
is not self
. In the second place, a data task publisher either sends a failure or it delivers one value followed by a completion, so that either way, the entire pipeline is released in good order almost immediately. The problem arises, as far as I can tell, when the property is a property of self
and the pipeline is never cancelled from upstream.
The workaround is to use .sink
instead of .assign
, because you can use the capture list to specify that self
is weak
or unowned
, breaking the cycle. So, assuming that image
is a property of self
, this might cause a retain cycle, depending on the behavior of the upstream publisher:
.assign(to: \.image, on: self)
If so, replace it with this:
.sink { [unowned self] in self.image = $0 }
The unowned self
breaks the retain cycle, and self
is able to go out of existence in the normal way.