Swift Combine 框架是 Apple 于 2019 年在 WWDC 上發(fā)布的,旨在簡化響應(yīng)式編程胞此。響應(yīng)式編程是一種編程范式睡蟋,專注于處理異步事件流款票,例如用戶輸入控硼、網(wǎng)絡(luò)請求或文件變化等。Combine 框架的主要目標(biāo)是通過統(tǒng)一和簡化事件處理邏輯艾少,減少傳統(tǒng)回調(diào)或通知機(jī)制的復(fù)雜性卡乾。
Combine 框架的核心思想是將數(shù)據(jù)變化表示為“發(fā)布者”(Publisher),它可以發(fā)出值缚够、完成事件或者錯誤事件幔妨。訂閱者(Subscriber)則會訂閱這些發(fā)布者,以響應(yīng)數(shù)據(jù)的變化谍椅。Combine 的發(fā)布-訂閱模式與其他響應(yīng)式編程庫(例如 RxSwift)類似误堡,但它與 Swift 和 Apple 的生態(tài)系統(tǒng)高度集成,專門為 iOS雏吭、macOS锁施、watchOS 和 tvOS 量身定制。
Combine 的出現(xiàn)解決了開發(fā)者在處理異步任務(wù)時可能遇到的挑戰(zhàn)杖们,使得代碼更加簡潔悉抵、易讀和易于維護(hù),同時提供了對 Swift 語言類型系統(tǒng)的強(qiáng)大支持摘完。
核心概念
在 Combine 框架中姥饰,處理響應(yīng)式編程的核心是四個基本概念:Publisher、Subscriber孝治、Subscription 和 Subject列粪。這些概念共同構(gòu)建了數(shù)據(jù)流的架構(gòu),允許我們在異步事件發(fā)生時響應(yīng)和處理數(shù)據(jù)谈飒。下面詳細(xì)解釋每個概念:
1. Publisher(發(fā)布者)
它定義了一個數(shù)據(jù)源岂座,可以發(fā)出值、完成事件或錯誤步绸。Publisher 會持續(xù)向它的訂閱者發(fā)出這些事件掺逼。Publisher 可以發(fā)出三種類型的事件:
- 值(Value): 發(fā)布的數(shù)據(jù)。
- 完成(Completion): 事件完成瓤介,發(fā)布者不會再發(fā)出任何事件吕喘。
- 錯誤(Failure): 出現(xiàn)錯誤,發(fā)布者停止發(fā)送事件刑桑,并向訂閱者發(fā)送錯誤氯质。
常見的內(nèi)置 Publisher 類型:
-
Just
: 只發(fā)布一個值,然后立即完成祠斧。 -
Empty
: 不發(fā)出任何值闻察,只發(fā)送完成或錯誤事件。 -
Future
: 異步產(chǎn)生一個值,并且只發(fā)送一次值或錯誤辕漂。 -
PassthroughSubject
: 允許外部手動發(fā)布值呢灶,不保留歷史值。 -
CurrentValueSubject
: 發(fā)布當(dāng)前值钉嘹,并對新訂閱者發(fā)送最新的值鸯乃。 -
Deferred
: 延遲創(chuàng)建 Publisher,直到有訂閱者時才執(zhí)行跋涣。 -
Fail
: 立即發(fā)送錯誤并完成缨睡,用于模擬失敗。 -
Timer.TimerPublisher
: 定期發(fā)送時間值陈辱,用于計時或輪詢奖年。 -
NotificationCenter.Publisher
: 發(fā)布通知中心的通知。 -
URLSession.DataTaskPublisher
: 處理網(wǎng)絡(luò)請求沛贪,發(fā)送響應(yīng)數(shù)據(jù)或錯誤陋守。
let publisher = Just(5)
publisher.sink(receiveCompletion: { print($0) }, receiveValue: { print($1) })
2. Subscriber(訂閱者)
Subscriber 是 Publisher 的消費(fèi)者。它接收來自 Publisher 發(fā)出的數(shù)據(jù)或事件鹏浅,并做出相應(yīng)處理嗅义。訂閱者通過訂閱(subscribe)特定的 Publisher 來監(jiān)聽它發(fā)出的值或狀態(tài)變化。
Subscriber 的關(guān)鍵在于它的兩個方法:
-
receive(_ input: Input)
: 接收發(fā)出的值隐砸。 -
receive(completion: Subscribers.Completion<Failure>)
: 接收完成或錯誤事件之碗。
Combine 提供了 sink
和 assign
等方便的方法來創(chuàng)建訂閱者。
let publisher = Just("Hello, Combine")
let subscriber = publisher.sink(
receiveCompletion: { print("Completion: \\($0)") },
receiveValue: { print("Value: \\($0)") }
)
3. Subscription(訂閱)
Subscription 是 Subscriber 和 Publisher 之間的橋梁季希。它管理數(shù)據(jù)流的生命周期褪那,控制訂閱者從發(fā)布者接收多少數(shù)據(jù)。它本質(zhì)上代表了一個訂閱操作式塌。
當(dāng)訂閱者開始監(jiān)聽一個 Publisher 時博敬,Publisher 會創(chuàng)建一個 Subscription,并將它傳遞給訂閱者峰尝。Subscriber 可以通過 Subscription 控制是否需要取消訂閱或者請求更多數(shù)據(jù)(例如在背壓機(jī)制中應(yīng)用)偏窝。
final class CustomSubscriber: Subscriber {
func receive(subscription: Subscription) {
print("Subscribed!")
subscription.request(.max(3)) // 只請求3個值
}
func receive(_ input: Int) -> Subscribers.Demand {
print("Received value: \\(input)")
return .none // 不請求更多值
}
func receive(completion: Subscribers.Completion<Never>) {
print("Completed")
}
}
4. Subject(主題)
Subject 是 Combine 框架中特殊類型的 Publisher,它既可以是 Publisher武学,也可以是 Subscriber祭往。它允許你手動發(fā)布值給訂閱者,非常適合用于橋接外部事件或多次觸發(fā)的值火窒。
Combine 中有兩種常見的 Subject:
-
PassthroughSubject
: 不保存任何值硼补,直接將接收到的值發(fā)布給訂閱者。 -
CurrentValueSubject
: 保存最后發(fā)布的一個值熏矿,新訂閱者會首先接收到當(dāng)前值已骇。
使用場景:
- 如果你想手動觸發(fā)某些值或事件离钝,可以使用 Subject。
- 當(dāng)你想讓多個訂閱者訂閱并立即收到當(dāng)前最新值時褪储,可以使用
CurrentValueSubject
卵渴。
let subject = PassthroughSubject<String, Never>()
let subscriber = subject.sink { value in
print("Received value: \\(value)")
}
subject.send("Hello")
subject.send("World")
操作符 Operators
在 Swift 的 Combine 框架中,Operators(操作符) 是用于對數(shù)據(jù)流進(jìn)行處理乱豆、轉(zhuǎn)換奖恰、過濾等操作的核心組件。操作符可以對從發(fā)布者(Publisher)發(fā)出的值進(jìn)行鏈?zhǔn)教幚硗鹪#愃朴诤瘮?shù)式編程中的 map、filter 等操作论泛。通過使用操作符揩尸,開發(fā)者可以靈活地對事件流進(jìn)行操作,以滿足各種需求屁奏。
Combine 中的操作符為響應(yīng)式編程提供了非常強(qiáng)大的數(shù)據(jù)處理能力岩榆。通過這些操作符,開發(fā)者可以輕松地處理和轉(zhuǎn)換異步事件流坟瓢,從而減少代碼復(fù)雜性并提高代碼的可維護(hù)性勇边。
以下是 Combine 中常用的一些操作符:
1. Transforming Operators(轉(zhuǎn)換操作符)
-
map
:將發(fā)布者發(fā)出的值進(jìn)行映射,轉(zhuǎn)換成另一個類型的值折联。let numbers = [1, 2, 3, 4] let publisher = numbers.publisher publisher .map { $0 * 2 } // 將每個值乘以 2 .sink { print($0) }
-
compactMap
:類似于map
粒褒,但可以過濾掉nil
值,只返回非空值诚镰。let numbers = ["1", "2", "a", "4"] let publisher = numbers.publisher publisher .compactMap { Int($0) } // 嘗試將字符串轉(zhuǎn)換為 Int奕坟,忽略不能轉(zhuǎn)換的值 .sink { print($0) }
2. Filtering Operators(過濾操作符)
-
filter
:只通過滿足條件的值,過濾掉不符合條件的元素清笨。let numbers = [1, 2, 3, 4, 5] let publisher = numbers.publisher publisher .filter { $0 % 2 == 0 } // 只允許偶數(shù)通過 .sink { print($0) }
-
removeDuplicates
:移除連續(xù)重復(fù)的值月杉。let numbers = [1, 1, 2, 3, 3, 3, 4] let publisher = numbers.publisher publisher .removeDuplicates() // 移除連續(xù)重復(fù)的值 .sink { print($0) }
3. Combining Operators(組合操作符)
-
merge
:將兩個或多個發(fā)布者的輸出合并成一個流。let pub1 = [1, 2, 3].publisher let pub2 = [4, 5, 6].publisher pub1 .merge(with: pub2) // 將兩個發(fā)布者的值合并 .sink { print($0) }
-
combineLatest
:當(dāng)兩個或多個發(fā)布者都發(fā)出值時抠艾,組合它們的最新值苛萎。let pub1 = PassthroughSubject<Int, Never>() let pub2 = PassthroughSubject<String, Never>() pub1 .combineLatest(pub2) // 組合最新的值 .sink { print("pub1: \\($0), pub2: \\($1)") }
-
zip
:與combineLatest
類似,但它只會在兩個發(fā)布者都有值時才會發(fā)出值检号,并且會按照順序組合腌歉。let pub1 = [1, 2, 3].publisher let pub2 = ["a", "b", "c"].publisher pub1 .zip(pub2) // 按順序組合兩個發(fā)布者的值 .sink { print($0, $1) }
4. Timing Operators(時間操作符)
-
debounce
:延遲發(fā)出值,直到指定時間段內(nèi)沒有新的值發(fā)出谨敛。let subject = PassthroughSubject<String, Never>() subject .debounce(for: .seconds(1), scheduler: DispatchQueue.main) // 1秒內(nèi)沒有值發(fā)出才進(jìn)行發(fā)射 .sink { print($0) }
-
delay
:延遲發(fā)布者的輸出究履。let publisher = Just("Hello") publisher .delay(for: .seconds(2), scheduler: DispatchQueue.main) // 延遲2秒后發(fā)出值 .sink { print($0) }
5. Error Handling Operators(錯誤處理操作符)
-
catch
:處理發(fā)布者在發(fā)出錯誤時,返回一個新的發(fā)布者脸狸。let publisher = Fail<Int, Error>(error: NSError(domain: "", code: -1, userInfo: nil)) publisher .catch { _ in Just(100) } // 捕獲錯誤并返回一個默認(rèn)值 .sink { print($0) }
-
retry
:如果發(fā)布者遇到錯誤最仑,可以重新嘗試訂閱指定次數(shù)藐俺。let publisher = Fail<Int, Error>(error: NSError(domain: "", code: -1, userInfo: nil)) publisher .retry(3) // 遇到錯誤時最多重試3次 .sink { print($0) }
6. Reducing Operators(歸約操作符)
-
reduce
:累積值,直到發(fā)布者完成并發(fā)出最終結(jié)果泥彤。let numbers = [1, 2, 3, 4] let publisher = numbers.publisher publisher .reduce(0) { $0 + $1 } // 累加所有的值 .sink { print($0) }
-
scan
:類似于reduce
欲芹,但會在每個新值到達(dá)時發(fā)出中間結(jié)果。let numbers = [1, 2, 3, 4] let publisher = numbers.publisher publisher .scan(0) { $0 + $1 } // 累加值并發(fā)出中間結(jié)果 .sink { print($0) }
高階 - Scheduler
Scheduler 控制代碼執(zhí)行上下文吟吝,決定數(shù)據(jù)流中發(fā)布者和訂閱者的運(yùn)行線程菱父。它是 Combine 中多線程和任務(wù)調(diào)度的核心,允許指定代碼在主線程剑逃、后臺線程或自定義隊列中執(zhí)行浙宜。常見應(yīng)用:后臺網(wǎng)絡(luò)請求、復(fù)雜計算蛹磺、延遲任務(wù)和輕量級頻繁任務(wù)處理粟瞬。合理使用 Scheduler 可優(yōu)化程序性能和用戶體驗。
Scheduler 的作用
- 線程管理:Scheduler 主要用于控制異步操作在哪個線程執(zhí)行萤捆,特別是在涉及網(wǎng)絡(luò)請求裙品、文件 I/O 或耗時操作時,確保它們不會阻塞主線程俗或,從而影響 UI 響應(yīng)市怎。
- 任務(wù)調(diào)度:通過 Scheduler,可以安排任務(wù)的執(zhí)行順序辛慰,例如:在后臺執(zhí)行復(fù)雜操作并在操作完成后切換回主線程更新 UI区匠。
- 頻率控制:使用 Scheduler 可以指定執(zhí)行頻率,如在延遲昆雀、間隔時間或某個特定的時間后執(zhí)行任務(wù)辱志。
Combine 框架通過使用 subscribe(on:)
和 receive(on:)
來指定發(fā)布者和訂閱者的執(zhí)行上下文。
-
subscribe(on:)
:用于指定數(shù)據(jù)發(fā)布的上下文狞膘,控制數(shù)據(jù)是在哪個 Scheduler(線程)上產(chǎn)生的揩懒。 -
receive(on:)
:用于指定數(shù)據(jù)接收的上下文,控制 Subscriber 接收數(shù)據(jù)并處理它時在哪個 Scheduler 上運(yùn)行挽封,常用于將操作結(jié)果傳回主線程已球。
典型的 Scheduler 類型
Combine 中的 Scheduler 主要分為幾種類型,常用于處理不同場景的調(diào)度需求:
-
DispatchQueue
: 最常用的調(diào)度器辅愿,允許你在主線程或后臺線程上執(zhí)行任務(wù)智亮。 -
RunLoop
: 主要用于事件驅(qū)動的循環(huán),適用于任務(wù)調(diào)度頻繁但任務(wù)執(zhí)行輕量的場景点待。 -
OperationQueue
: 用于管理基于隊列的任務(wù)執(zhí)行順序和優(yōu)先級阔蛉。 -
ImmediateScheduler
: 在當(dāng)前線程上立即執(zhí)行任務(wù)。
典型的應(yīng)用場景
1. 在后臺線程執(zhí)行網(wǎng)絡(luò)請求癞埠,主線程更新 UI
在 UI 應(yīng)用中状原,通常需要在后臺執(zhí)行耗時任務(wù)(如網(wǎng)絡(luò)請求或數(shù)據(jù)庫操作)聋呢,然后在主線程更新 UI〉咔可以通過 subscribe(on:)
切換到后臺線程削锰,處理數(shù)據(jù)后通過 receive(on:)
切換到主線程更新 UI。
let publisher = URLSession.shared.dataTaskPublisher(for: URL(string: "<https://example.com>")!)
.subscribe(on: DispatchQueue.global(qos: .background)) // 在后臺執(zhí)行網(wǎng)絡(luò)請求
.receive(on: DispatchQueue.main) // 在主線程接收并更新 UI
.sink(receiveCompletion: { completion in
print("Request completed: \\(completion)")
}, receiveValue: { data, response in
print("Data received: \\(data)")
})
2. 延遲任務(wù)執(zhí)行
有時候需要讓某些任務(wù)延遲執(zhí)行或在特定時間間隔執(zhí)行毕莱∑鞣罚可以通過 DispatchQueue
調(diào)度任務(wù)執(zhí)行。
let publisher = Just("Hello")
.delay(for: .seconds(3), scheduler: DispatchQueue.main) // 延遲3秒后執(zhí)行
.sink { value in
print("Received after delay: \\(value)")
}
3. 頻繁計算任務(wù)調(diào)度
對于一些頻繁且高優(yōu)先級的任務(wù)朋截,比如基于用戶輸入的實時計算(如實時搜索)蛹稍,可以通過 RunLoop
來執(zhí)行調(diào)度,因為 RunLoop
對于高頻率的輕量任務(wù)處理效率較高质和。
let publisher = Just("Search Query")
.receive(on: RunLoop.main) // 在主線程的事件循環(huán)中進(jìn)行
.sink { value in
print("Received on RunLoop: \\(value)")
}
4. 避免主線程阻塞
在執(zhí)行復(fù)雜的計算任務(wù)時稳摄,使用 subscribe(on:)
將計算操作移至后臺,避免阻塞主線程饲宿,確保 UI 的流暢性。
let heavyComputation = Just(42)
.map { value in
// 執(zhí)行一些耗時的計算
return (0..<1000000).reduce(value, +)
}
.subscribe(on: DispatchQueue.global(qos: .userInitiated)) // 在后臺線程執(zhí)行計算
.receive(on: DispatchQueue.main) // 在主線程更新 UI
.sink { result in
print("Computation result: \\(result)")
}
總結(jié)
Swift Combine 是 Apple 在 2019 年推出的響應(yīng)式編程框架胆描,旨在簡化異步事件流的處理瘫想。Combine 中的核心概念有四個,分別是 發(fā)布數(shù)據(jù)或事件的源頭 Publisher昌讲、接收來自 Publisher 的數(shù)據(jù)或事件 的 Subscriber国夜、管理 Publisher 和 Subscriber 之間的關(guān)系 的 Subscription 、用于橋接外部數(shù)據(jù)源并手動控制數(shù)據(jù)流的 Subject短绸。
除了4個基本概念车吹,還有一些很實用的操作符:
- 轉(zhuǎn)換操作符:如 map、compactMap
- 過濾操作符:如 filter醋闭、removeDuplicates
- 組合操作符:如 merge窄驹、combineLatest、zip
- 時間操作符:如 debounce证逻、delay
- 錯誤處理操作符:如 catch乐埠、retry
- 歸約操作符:如 reduce、scan
Scheduler 是 Combine 的進(jìn)階內(nèi)容囚企,可以簡單了解一下丈咐,需要的時候再看文檔
Combine 框架通過提供統(tǒng)一的事件處理方式,簡化了異步編程龙宏,提高了代碼的可讀性和可維護(hù)性棵逊。它與 Swift 和 Apple 生態(tài)系統(tǒng)高度集成,為 iOS银酗、macOS辆影、watchOS 和 tvOS 開發(fā)提供了強(qiáng)大的響應(yīng)式編程工具徒像。