swift-Combine 學(xué)習(xí)筆記1

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孝治、SubscriptionSubject列粪。這些概念共同構(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 提供了 sinkassign 等方便的方法來創(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)式編程工具徒像。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秸歧,隨后出現(xiàn)的幾起案子厨姚,更是在濱河造成了極大的恐慌,老刑警劉巖键菱,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬墙,死亡現(xiàn)場離奇詭異,居然都是意外死亡经备,警方通過查閱死者的電腦和手機(jī)拭抬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侵蒙,“玉大人造虎,你說我怎么就攤上這事》坠耄” “怎么了算凿?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犁功。 經(jīng)常有香客問我氓轰,道長,這世上最難降的妖魔是什么浸卦? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任署鸡,我火速辦了婚禮,結(jié)果婚禮上限嫌,老公的妹妹穿的比我還像新娘靴庆。我一直安慰自己,他們只是感情好怒医,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布炉抒。 她就那樣靜靜地躺著,像睡著了一般裆熙。 火紅的嫁衣襯著肌膚如雪端礼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天入录,我揣著相機(jī)與錄音蛤奥,去河邊找鬼。 笑死僚稿,一個胖子當(dāng)著我的面吹牛凡桥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚀同,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缅刽,長吁一口氣:“原來是場噩夢啊……” “哼啊掏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衰猛,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤迟蜜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啡省,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娜睛,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年卦睹,在試婚紗的時候發(fā)現(xiàn)自己被綠了畦戒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡结序,死狀恐怖障斋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徐鹤,我是刑警寧澤垃环,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站返敬,受9級特大地震影響晴裹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜救赐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望只磷。 院中可真熱鬧经磅,春花似錦、人聲如沸钮追。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽元媚。三九已至轧叽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刊棕,已是汗流浹背炭晒。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甥角,地道東北人网严。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像嗤无,于是被迫代替她去往敵國和親震束。 傳聞我的和親對象是個殘疾皇子怜庸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容