前言
“一個隨時間處理數(shù)據(jù)的聲明式的 Swift API。”Combine 蘋果采用的一種函數(shù)響應(yīng)式編程的庫,類似于 RxSwift 。Combine 使用了許多在其他語言和庫中可以找到的相同的函數(shù)響應(yīng)概念,并將Swift的靜態(tài)類型特性應(yīng)用到其解決方案中。
像是 React Native 和 Flutter 這樣的移動端跨平臺方案,由于采用了聲明式 UI 的編寫方式和嚴(yán)格的數(shù)據(jù)流動方向,就能夠大幅減輕開發(fā)者的思考負(fù)擔(dān)。
SwiftUI 很明顯也吸收了這些現(xiàn)代的編程思想,在另一個重量級系統(tǒng)框架 Combine 的協(xié)助下,實(shí)現(xiàn)了單一數(shù)據(jù)源的管理。
響應(yīng)式編程的核心是將所有事件轉(zhuǎn)化成為異步的數(shù)據(jù)流,這剛好就是 Combine 的主要功能。Combine 采用觀察者模式,對應(yīng)多個觀察者,可以分別訂閱感興趣的內(nèi)容。在 SwiftUI 的界面布局過程中,不同的 View 就是觀察者,分別訂閱了相關(guān)聯(lián)的屬性,并在數(shù)據(jù)發(fā)生變化之后就能夠自動的重新渲染。
1、Pulishers、Operators、Subscribers
Pulishers:發(fā)布者,負(fù)責(zé)提供數(shù)據(jù)(當(dāng)數(shù)據(jù)可用且獲得請求)。一個發(fā)布者如果沒有訂閱,則不會發(fā)布任何數(shù)據(jù)。當(dāng)你在描述一個發(fā)布者時,你會用兩種相關(guān)類型(associatedtype)來表述他:Output 和 Failure 。比如發(fā)布者返回 String實(shí)例 ,并且可能以 URLError實(shí)例 的形式返回失敗,那么發(fā)布者可以用 <String, URLError> 來描述。
Subscribers:訂閱者,負(fù)責(zé)(向發(fā)布者)請求數(shù)據(jù)和接收發(fā)布者提供的數(shù)據(jù)(或者失敗信息)。訂閱者用兩種相關(guān)類型進(jìn)行描述:Input 和 Failure 。訂閱者發(fā)起數(shù)據(jù)請求,并空值接收到的數(shù)據(jù)量。在 Combine 中,他可以看作是“行為的驅(qū)動者”,沒有了訂閱者,其他的組成部分將閑置。
發(fā)布者和訂閱者是相互連接的,并構(gòu)成 Combine 的核心。當(dāng)你連接一個訂閱者到發(fā)布者上,Input 和 Output 類型必須一致,兩者的 Failure 也需要一致。
Operators:操作者是一個行為類似訂閱者和發(fā)布者的對象。他既實(shí)現(xiàn)了 Publisher協(xié)議 ,又實(shí)現(xiàn)了 Subscriber協(xié)議 。他們支持訂閱一個發(fā)布者,并接收訂閱者的請求。

一般的數(shù)據(jù)流是這樣處理的:發(fā)布者 -> 操作者1 -> 操作者2 -> ... -> 操作者n -> 訂閱者
操作者可以被用來轉(zhuǎn)換數(shù)值或者值的類型 -- Output 和 Failure 均可。操作者也可以分割、復(fù)制、合并數(shù)據(jù)流。操作者之間的 Output/Failure類型 必須一致,否則編譯器會報錯。
2、Future、Promise
Future:未來某個時刻會發(fā)布一個數(shù)據(jù),會立即結(jié)束,并且會帶有一個狀態(tài),是成功還是失敗的狀態(tài)。(類似我們Swift中的逃逸閉包)
final public class Future<Output, Failure> : Publisher where Failure : Error {
public typealias Promise = (Result<Output, Failure>) -> Void
public init(_ attemptToFulfill: @escaping (@escaping Future<Output, Failure>.Promise) -> Void)
final public func receive<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}
查看源碼,包含一個Promise類型及一個逃逸閉包的初始化函數(shù)。Future和Promise結(jié)合使用,一個未來要給的承諾,也就是未來執(zhí)行的操作返回的一個最終結(jié)果。初始化函數(shù)中可以看出Promise為接收單個Result類型的閉包。
拓展:原理上Future和PassthroughSubject、CurrentValueSubject很類似,F(xiàn)uture遵循Publisher協(xié)議,后兩者遵循的是Subject協(xié)議,可以直接使用send方法發(fā)送數(shù)據(jù)。
3、簡單示例
創(chuàng)建一個Future類型的閉包任務(wù)(發(fā)布者),即一個將會在未來某時刻調(diào)用的閉包,閉包會返回字符串3,沒有錯誤返回,:
let futurePublisher = Future<String, Never> { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
promise(.success("3"))
}
}
新增ViewModel數(shù)據(jù)管理類,遵循ObservableObject協(xié)議,即被@Published修飾符修飾的屬性title改變時,就會發(fā)布通知給使用到此屬性的View刷新。
extension ContentView {
class ViewModel: ObservableObject {
private var cancellables = Set<AnyCancellable>()
//刷新視圖用的變量
@Published var title: String = "Hello Lcr"
func fetchData() {
futurePublisher.print("_fetchData_")
.receive(on: RunLoop.main)
.sink { completion in
switch completion {
case .failure(let err):
print("Error is \(err.localizedDescription)")
case .finished:
print("Finished")
}
} receiveValue: { [weak self] data in
print("fetchWebData: \(data)")
self?.title = data
}
.store(in: &cancellables)
}
}
}
關(guān)于futurePublisher.sink{} receiveValue{}函數(shù),就是在訂閱者和發(fā)布者之間橋梁,以及后續(xù)接收數(shù)據(jù)操作。
Use
Publisher/sink(receiveCompletion:receiveValue:)to observe values received by the publisher and process them using a closure you specify.
訂閱者可以通過sink函數(shù)響應(yīng)調(diào)用,block區(qū)域?qū)盏絧ublisher發(fā)出的values,publisher可以發(fā)射0個或多個values,除了基本值之外,您的publisher還會發(fā)給訂閱者特殊值。如.finished(完成)、.failure()(失?。?/p>
struct ContentView: View {
@StateObject var vm = ViewModel()
var body: some View {
Text(vm.title).padding().onAppear{
vm.fetchData()
}
}
}
訂閱者Text通過vm操作者去向發(fā)布者索要數(shù)據(jù),futurePublisher閉包會執(zhí)行,2秒后將Promise閉包執(zhí)行將數(shù)據(jù)返回,receiveValue接收到數(shù)據(jù),保存至cancellables,狀態(tài)為finished,即任務(wù)到此結(jié)束。

4、backPresssure
對于大多數(shù)響應(yīng)式編程場景而言,訂閱者不需要對發(fā)布過程進(jìn)行過多的控制。當(dāng)發(fā)布者發(fā)布元素時,訂閱者只需要無條件地接收即可。但是,如果發(fā)布者發(fā)布的速度過快,而訂閱者接收的速度又太慢,我們該怎么解決這個問題呢?Combine 已經(jīng)為我們制定了穩(wěn)健的解決方案!現(xiàn)在,讓我們來了解如何施加背壓(back pressure,也可以叫反壓)以精確控制發(fā)布者何時生成元素。
在 Combine 中,發(fā)布者生成元素,而訂閱者對其接收的元素進(jìn)行操作。不過,發(fā)布者會在訂閱者連接和獲取元素時才發(fā)送元素。訂閱者通過 Subscribers.Demand 類型來表明自己可以接收多少個元素,以此來控制發(fā)布者發(fā)送元素的速率。
訂閱者可以通過兩種方式來表明需求(Demand):
- 調(diào)用 Subscription 實(shí)例(由發(fā)布者在訂閱者進(jìn)行第一次訂閱時提供)的 request(_:) 方法;
- 在發(fā)布者調(diào)用訂閱者的 receive(_:) 方法來發(fā)送元素時,返回一個新的 Subscribers.Demand 實(shí)例;
下面利用一個簡單例子演示一下:
let width = UIScreen.main.bounds.width, height = UIScreen.main.bounds.height
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton.init(frame: CGRect.init(x: (width-180)/2, y: 420, width: 180, height: 40))
button.addTarget(self, action: #selector(tapped(button:)), for: .touchUpInside)
button.setTitle("訂閱 timerPublisher", for: .normal)
button.backgroundColor = .orange
button.layer.cornerRadius = 10
view.addSubview(button)
}
@objc func tapped(button: UIButton) {
// 訂閱
print ("開啟訂閱 \(Date())")
timerPub.subscribe(MySubscriber())
}
}
// 發(fā)布者: 使用一個定時器來每秒發(fā)送一個日期對象
let timerPub = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
// 訂閱者: 在訂閱以后,等待2秒,然后請求最多3個值
class MySubscriber: Subscriber {
// typealias Input = Date
// typealias Failure = Never
// var subscription: Subscription?
func receive(subscription: Subscription) {
print("訂閱接收到了")
// self.subscription = subscription
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
subscription.request(.max(3))
}
}
func receive(_ input: Date) -> Subscribers.Demand {
print("發(fā)布時間:\(input)——————接收時間:\(Date())")
return Subscribers.Demand.none
}
func receive(completion: Subscribers.Completion<Never>) {
print("完成")
}
}
struct ContentView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> ViewController {
return ViewController()
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {}
}

可見訂閱者通過 Subscribers.Demand 類型來表明自己可以接收多少個元素,以此來控制發(fā)布者發(fā)送元素的速率。