I recently worked on SwiftUI and starting writing code in a declarative way. But here comes a confusion. Like what is shown below, I want to (1)load the song data and (2)show the view by setting isInfoViewShown
, after song
is assigned to any value.
I assume didSet{}
and Combine @Published .sink{}
are doing things interchangeably. So I want to ask what are the differences between them? And in my own opinion, didSet{}
can do most of jobs Combine
do. So why should Apple announce Combine
framework?
Any help is appreciated.
class InfoViewModel: ObservableObject {
@Published var song: Song? {
didSet { // Here's the didSet{}: [1] load song data
if let song = song {
load(song: song)
}
}
}
private var songSelectedSubscription: AnyCancellable?
@Published var isInfoViewShown: Bool = false
init() { // Here's the Combine @Published .sink{}: [2] show the view
songSelectedSubscription = $song.sink{ self.isInfoViewShown = ($0 == nil ? false : true) }
}
}
CodePudding user response:
Sure, there are lots of ways to observe changes to data, KVO, Notification Center, didSet, combine etc, so in one sense these things are indeed similar. Differences though are:
a property can only have one didSet, which makes it hard for any number of observers to register an interest in the property.
But the big win with Combine is: a Combine pipeline allows you to create streams easily where you can say for example, transform the stream of changes to a stream that: observes changes to the user's input string, debounces it (rate limiting changes so we don't spam the server), filter those changes for any value that is at least 3 characters long, and that produces a new stream which you can observe. Map/FlatMap is also a really important combine operator, transform a stream of a's into a stream of b's. Also merging two streams together with combineLatest and so on, so you can take a stream of a's and stream of b's and make a stream of (a, b)'s and then map that to a stream of validated c's, for example.
CodePudding user response:
ObservableObject
is designed to hold the model structs. For view data like isInfoViewShown
that should be @State
in the View
struct. In SwiftUI the View
struct is comparable to the view model object in UIKit it looks like you are used to. Since it is a struct (value) type is faster and less error-prone. You can extract related @State var
into their own struct using mutating func
for testable logic which you probably did in your view model objects. A View
struct will automatically call body when a let
or a @State var
value changes, this dependency tracking is built-in to SwiftUI, you no-longer need to use Combine for this like you might in UIKit.
FYI when we use Combine inside the ObservableObject
we assign
the end of the pipeline to the @Published
we don't tend to use sink
because then we would need to perform manual cancellation in deinit
whereas assign
does that automatically. Inside the @Published
auto-genenerated willSet
, it calls objectWillChange.send()
which SwiftUI coalesces multiple of into a single recalculation of body
in any View
struct that use @StateObject
, @ObservedObject
or @EnvironmentObject
(regardless of the object's properties accessed or not - let
and @State var
are more optimal in that regard).