0%

Swift Combine 框架

Swift Combine 框架

[TOC]
網路上有許多關於Swift Combine框架的應用但許多都只有部分說明,故整合了幾個網站的資料以便能完整的學習。

簡介

在現代 GUI 編程中,開發者往往會處理大量事件(例如網絡請求,屏幕輸入,系統通知等),根據事件去讓用戶界面發生變化。而對異步事件的處理,會讓代碼和狀態變得尤其複雜。為了幫助開發者簡化異步編程,使代碼更加簡潔、易於維護,蘋果在 WWDC 2019 發布了基於 Swift 的響應式異步編程框架 —— Combine。

在 Combine 中,有幾個重要的組成部分:

  • 發佈者 Publiser:負責發佈數據
  • 訂閱者 Subscriber:負責訂閱數據
  • 操作符 Operator:負責在 Publisher 和 Subscriber 之間進行數據的轉換。

Combine 框架帶有所謂的發布者和訂閱者。
如同RxSwift:
Publishers = Observables
Subscribers = Observers

發佈者 Publiser

Publisher 的主要工作是隨著時間推移向一個或多個 Subscriber 發佈數據或者事件。本質上說,Publisher 的工作其實僅有兩個——被 Subscriber 訂閱、發布數據和事件。

Combine 內置的 Publisher

Combine 為我們內置了一些 Publisher 以滿足我們開發的需要。一般情況下內置的 Publisher 已經夠用,不需要再自定義了,Apple 也不推薦我們這麼做。下面列出了一些常用的 Publisher 以供參考:

  • Just:只提供一個數據然後終止的 Publisher,失敗類型為 Never。
  • Future:異步操作的 Publisher,用一個閉包初始化,該閉包最終解析為單個輸出數據或失敗。
  • Deferred:在運行提供的閉包之前等待訂閱的 Publisher ,以便為新的 Subscriber 創建 Publisher 。
  • @Published:屬性包裝器,用來把一個屬性數據轉變為 Publisher。
  • Empty:一個從不發布任何數據的 Publisher ,並且可以選擇立即完成。
  • Fail:立即使用指定錯誤終止的 Publisher 。
  • Optional:如果可選數據具有數據,則 Publisher 僅向每個 Subscriber 發布一次可選數據。
  • Sequence:發布給定數據序列的 Publisher 。
  • Record:允許記錄一系列 Input 和 Completion,供每個 Subscriber 回放。
  • Share:實現者為類的 Publisher ,其行為與其上游 Publisher 相同。
  • Multicast:多播 Publisher ,當有多個 Subscriber,但希望上游 Publisher 的每個數據僅調用一次 receive (_:) 時使用。
  • ObservableObject:配合 SwiftUI 一起使用,符合 ObservableObject 協議的對象可以提供 Publisher。

訂閱者 Subscriber

Subscriber相當於RxSwift中的Observer。

Publisher 根據 Subscriber 的請求提供數據。如果沒有任何訂閱請求,Publisher 不會主動發布任何數據。所以說,Subscriber 負責向 Publisher 請求數據並接收數據(也可能會獲取失敗)。

Combine 內置的 Subscriber

Sink

Sink 在閉包中處理收到的數據或者 completion 事件。每當收到新值時,就會調用 receiveValue 閉包。處理 completion 事件的 receiveCompletion 閉包是可選的,這個閉包會在接收到 Publisher 終止的消息後調用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// 發送單個數據
let publisher = Just(1)

//進行訂閱
let subscription = publisher.sink { completion in

switch completion {
case .finished:
break

case .failure(let error):
print(error)
break
}
} receiveValue: { value in
print(value)
}

Assign

Assign 可以通過傳入類中某個屬性的 KeyPath,來將這個屬性的值設置為 Publisher 的 Output。通過 Assign,我們可以直接把發布的值綁定到數據模型或者 UI 控件的屬性上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// 創建對象
class Test {
@Published var str: String = "Hi"
}

let obj = Test()
print(obj.str)

let publisher = Just("Rex")
// assign訂閱,設置到Test的@Published屬性上
publisher.assign(to: &obj.$str) //&和$不能省

print(obj.str)
/* 輸出:
Hi
Rex
*/

Subject

Subject是一種特殊的 Publisher,Subject 的最大特點就是可以手動發送數據。

Combine 内置的 Subject

Combine 為我們內置了兩種 Subject —— PassthroughSubject 與 CurrentValueSubject,它們的區別主要在於是否會對收到的數據進行保留。

PassthroughSubject

PassthroughSubject 通過 send 發送數據或事件給下游的 Publisher 或 Subscriber, 不會對接收到的數據進行保留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let subject = PassthroughSubject<String, Never>() // 創建PassthroughSubject

// 訂閱
let subscription = subject.sink(receiveCompletion: { _ in
print("receiveCompletion")
}, receiveValue: { value in
print(value)
})

// 發送數據
subject.send("hello")
subject.send("world")
subject.send(completion: .finished)

//已收到完成事,解除訂閱,這裡不會有作用
subject.send("Rex")


/* 输出:
hello
world
receiveCompletion
*/

CurrentValueSubject

與 PassthroughSubject 不同,CurrentValueSubject 會保留一個最後的數據,並在被訂閱時將這個數據發送給下游的 Publisher 或 Subscriber。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let subject = CurrentValueSubject<String, Never>("hi") // 創建CurrentValueSubject,需要给它一個初始值
// CurrentValueSubject有value屬性
print(subject.value)

// 發送數據
subject.send("hello")
print(subject.value)

subject.send("world")
print(subject.value)

// 訂閱
let subscription = subject.sink { value in
print(value)
}

/* 输出:
hi
hello
world
world
*/

操作符 Operator

默認情況下,訂閱某個 Publisher,Subscriber 中的 Input 和 Failure 要與 Publisher 的 Output 和 Failure 類型相同。然而實際開發中往往並沒有這麼理想,此時就要藉助 Operator 進行轉換。 Operator 遵守 Publisher 協議,負責從數據流上游的 Publisher 訂閱值,經過轉換生成新的 Publisher 發送給下游的 Subscriber。

  • 轉換操作符( map、flatmap … )
  • 過濾操作符( filter … )
  • reduce 操作符( reduce … )
  • 運算操作符( count、min/max … )
  • 匹配操作符( contains … )
  • 序列操作符( prefix、first、last … )
  • 組合操作符( merge、zip … )
  • 錯誤處理操作符( catch、retry … )
  • 時間控制操作符( delay、timeout … )
  • 其他操作符( encode、decode … )

Cancellable

在實際開發中,當 Subscriber 在某個時候不想接收 Publisher 發布的數據時,可以取消訂閱以釋放資源。 Combine 中提供了 Cancellable 協議,該協議中定義了一個 cancel 方法,用於取消訂閱流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let subject = PassthroughSubject<String, Never>() // 創建PassthroughSubject

// 訂閱
let subscription = subject.sink(receiveCompletion: { _ in
print("receiveCompletion")
}, receiveValue: { value in
print(value)

})
// 發送數據
subject.send("hello")
// 中途取消訂閱
subscription.cancel()
// 後面發送的數據都會失敗
subject.send("world")
subject.send(completion: .finished)

/* 輸出:
hello
*/

資料出處:
https://louyu.cc/articles/ios-swift/2021/03/?p=2803/
https://www.icodesign.me/posts/swift-combine/
https://www.avanderlee.com/swift/combine/

tags: 公司分享會