So I'm digging into combine and this question came up.
Is there any real difference between using CurrentValueSubject
(and setting its value using currentValueSubject.value
) or using a @Published var
and accessing its publisher with a $
? I mean I know one returns a Subject
instead of a Publisher
, but the only real difference I could find is that CurrentValueSubject
is way more useful because you can declare it on a protocol.
I really don't understand how @Published
can be useful if we can just use PassthroughSubject
, am I missing something here?
Mind you, this is using UIKit, it may have other uses for SwiftUI.
Thank you.
@Published is one of the property wrappers in SwiftUI that allows us to trigger a view redraw whenever changes occur. You can use the wrapper combined with the ObservableObject protocol, but you can also use it within regular classes.
A subject that wraps a single value and publishes a new element whenever the value changes.
A PassthroughSubject broadcasts elements to downstream subscribers and provides a convenient way to adapt existing imperative code to Combine. As the name suggests, this type of subject only passes through values meaning that it does not capture any state and will drop values if there aren't any subscribers set.
In this story, I will explain what is CurrentValueSubject with an example. It is a custom publisher that has an initial value. Let’s imagine a screen that will be showing an empty view or non-empty list based on upcoming data from a web service. Personally, It is hard to manage these states in iOS projects.
Both PassthroughSubject and CurrentValueSubject are valuable parts of the Combine framework. Unlike custom publishers, they are relatively easy to work with and allow sending values over Combine streams. Subscribers will receive newly emitted values and can respond to them accordingly.
The value of a CurrentValueSubject can be set using the send method or by directly assigning it to the value property. The subject can also be finished, either successfully or with an error. Implementors can subscribe to events using the sink method: Which in case of a successful upload, prints out all different states:
CurrentValueSubject = A light switchSomeone turns on the lights in your home when you are outside. You get back home and you know someone has turned them on. CurrentValueSubject has an initial state, it retains the data you put in as its state.
@Published is just a quick way to use CurrentValueSubject a little neater. When I debug one of my apps and look at the type returned by $paramName , it's actually just a CurrentValueSubject:
po self.$books
▿ Publisher
▿ subject : <CurrentValueSubject<Array<Book>, Never>: 0x6000034b8910>
I guess one benefit of using CurrentValueSubject instead of @Published may be to allow you to use the error type?
Note: Despite being a CurrentValueSubject right now I'd never rely on that assumption.
CurrentValueSubject
is a value, a publisher and a subscriber all in one.
Sadly it doesn’t fire objectWillChange.send()
when used inside an ObservableObject.
You can specify an error type.
@Published
is a property wrapper, thus:
@Published
automatically fires objectWillChange.send()
when used inside an ObservableObject.
Xcode will emit a warning if your try to publish to @Published
wrapped property from a background queue. Probably because objectWillChange.send()
must be called from the main thread.
The error type of its publisher is Never
My biggest beef against @Published
is that it can’t behave as a subscriber and setting up Combine pipelines requires additional plumbing compared to a Current Value Subject.
We can declare a @Published
property inside a protocol. Not very pretty...
protocol TestProtocol {
var isEnabled: Bool { get }
var isEnabledPublished: Published<Bool> { get }
var isEnabledPublisher: Published<Bool>.Publisher { get }
}
class Test: ObservableObject, TestProtocol {
@Published var isEnabled: Bool = false
var isEnabledPublished: Published<Bool> { _isEnabled }
var isEnabledPublisher: Published<Bool>.Publisher { $isEnabled }
}
I found myself coming back to this post so felt I'd add some extra insight in to the difference between @Published
and CurrentValueSubject
.
One main difference can be found in the documentation for @Published
:
When the property changes, publishing occurs in the property’s willSet block, meaning subscribers receive the new value before it’s actually set on the property.
Additionally, conversation on the Swift Forums note that @Published
is intended for use with SwiftUI.
With regards to @Published
publishing in the willSet
block of it's property, consider the following example:
class PublishedModel {
@Published var number: Int = 0
}
let pModel = PublishedModel()
pModel.$number.sink { number in
print("Closure: \(number)")
print("Object: \(pModel.number) [read via closure]")
}
pModel.number = 1
print("Object: \(pModel.number) [read after assignment]")
This produces the following output:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 0 [read via closure]
Object: 1 [read after assignment]
Contrast this with another example where we keep everything the same, except replacing @Published
with CurrentValueSubject
:
class CurrentValueSubjectModel {
var number: CurrentValueSubject<Int, Never> = .init(0)
}
let cvsModel = CurrentValueSubjectModel()
cvsModel.number.sink { number in
print("Closure: \(number)")
print("Object: \(cvsModel.number.value) [read via closure]")
}
cvsModel.number.send(1)
print("Object: \(cvsModel.number.value) [read after assignment]")
Output:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 1 [read via closure] // <— Here is the difference
Object: 1 [read after assignment]
After updating number
to 1, reading the object's CurrentValueSubject
's value property within the closure prints the new value instead of the old value as with @Published
.
In summary, use @Published
within your ObservableObjects
for your SwiftUI views. If you're looking to create some sort of model object with an instance property that holds a current value and also publishes it's changes after they are set, use CurrentValueSubject
.
One advantage on @Published
is that it can act as a private-mutable, public-immutable CurrrentValueSubject.
Compare:
@Published private(set) var text = "someText"
with:
let text = CurrentValueSubject<String, Never>("someText")
When designing APIs you often want to allow clients to read the current value and subscribe to updates but prevent them from setting values directly.
There is one limitation when using @Published.
You can only use @Published on properties of a Class whereas CurrentValueSubject can be used for struct as well
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With