Combine 框架,從0到1 —— 5.Combine 中的 Subjects

 

本文首發於 Ficow Shen’s Blog,原文地址: Combine 框架,從0到1 —— 5.Combine 中的 Subjects

 

內容概覽

  • 前言
  • PassthroughSubject
  • CurrentValueSubject
  • Subject 作為訂閱者
  • 常見用法
  • 總結

 

前言

 

正所謂,工欲善其事,必先利其器。在開始使用 Combine 進行響應式編程之前,建議您先了解 Combine 為您提供的各種發布者(Publishers)、操作符(Operators)、訂閱者(Subscribers)。

Subject 是一類比較特殊的發布者,因為它同時也是訂閱者。Combine 提供了兩類 SubjectPassthroughSubjectCurrentValueSubject

 

如果您想了解更多 Publishers 的用法和注意事項,可以閱讀:Combine 框架,從0到1 —— 5.Combine 提供的發布者(Publishers)

 

PassthroughSubject

官網文檔

 

PassthroughSubject 可以向下游訂閱者廣播發送元素。使用 PassthroughSubject 可以很好地適應命令式編程場景。

如果沒有訂閱者,或者需求為0,PassthroughSubject 就會丟棄元素。

示例程式碼:

final class SubjectsDemo {
    
    private var cancellable: AnyCancellable?
    private let passThroughtSubject = PassthroughSubject<Int, Never>()
    
    func passThroughtSubjectDemo() {
        cancellable = passThroughtSubject
            .sink {
                print(#function, $0)
            }
        passThroughtSubject.send(1)
        passThroughtSubject.send(2)
        passThroughtSubject.send(3)
    }
}

輸出內容:

passThroughtSubjectDemo() 1
passThroughtSubjectDemo() 2
passThroughtSubjectDemo() 3

 

CurrentValueSubject

官網文檔

 

CurrentValueSubject 包裝一個值,當這個值發生改變時,它會發布一個新的元素給下游訂閱者。

CurrentValueSubject 需要在初始化時提供一個默認值,您可以通過 value 屬性訪問這個值。在調用 send(_:) 方法發送元素後,這個快取值也會被更新。

示例程式碼:

final class SubjectsDemo {
    
    private var cancellable: AnyCancellable?
    private let currentValueSubject = CurrentValueSubject<Int, Never>(1)
    
    func currentValueSubjectDemo() {
        cancellable = currentValueSubject
            .sink { [unowned self] in
                print(#function, $0)
                print("Value of currentValueSubject:", self.currentValueSubject.value)
            }
        currentValueSubject.send(2)
        currentValueSubject.send(3)
    }
}

輸出內容:

currentValueSubjectDemo() 1
Value of currentValueSubject: 1
currentValueSubjectDemo() 2
Value of currentValueSubject: 2
currentValueSubjectDemo() 3
Value of currentValueSubject: 3

 

Subject 作為訂閱者

 

示例程式碼:

final class SubjectsDemo {
    
    private var cancellable1: AnyCancellable?
    private var cancellable2: AnyCancellable?
    
    private let optionalCurrentValueSubject = CurrentValueSubject<Int?, Never>(nil)
    
    private func subjectSubscriber() {
        cancellable1 = optionalCurrentValueSubject
            .sink {
                print(#function, $0)
            }
        cancellable2 = [1, 2, 3].publisher
            .subscribe(optionalCurrentValueSubject)
    }
}

optionalCurrentValueSubject 可以作為一個訂閱者去訂閱序列發布者 [1, 2, 3].publisher 發送的元素。

輸出內容:

subjectSubscriber() nil
subjectSubscriber() Optional(1)
subjectSubscriber() Optional(2)
subjectSubscriber() Optional(3)

 

常見用法

 

在使用 Subject 時,我們往往不會將其暴露給調用方。這時候,可以使用 eraseToAnyPublisher 操作符來隱藏內部的 Subject

示例程式碼:

    struct Model {
        let id: UUID
        let name: String
    }
    
    final class ViewModel {
        private let modelSubject = CurrentValueSubject<Model?, Never>(nil)
        var modelPublisher: AnyPublisher<Model?, Never> {
            return modelSubject.eraseToAnyPublisher()
        }
        
        func updateName(_ name: String) {
            modelSubject.send(.init(id: UUID(), name: name))
        }
    }

外部調用者無法直接操控 ViewModel 內部的 Subject,這樣可以讓 ViewModel 更好地面對將來可能的改動。
外部調用者只需要知道 modelPublisherAnyPublisher<Model?, Never> 類型的發布者即可,無論內部採用了 CurrentValueSubject 還是 PassthroughSubject 甚至是其他的發布者。

 

總結

 

相比於其他的發布者來說, Subject 是比較容易理解的,而且也是最常用到的。
只可惜,對比 Rx 提供的 Subject,Combine 中的 Subject 無法設置緩衝的大小。也許某天蘋果會對此做出調整吧~