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!


Assign

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.


Table of Contents