Home > Software design >  What's the differences between `Combine` and `didSet` in Swift?
What's the differences between `Combine` and `didSet` in Swift?

Time:08-24

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).

  • Related