Swift Combine 學習(四):操作符 Operator

  • Swift Combine 學習(一):Combine 初印象
  • Swift Combine 學習(二):發布者 Publisher
  • Swift Combine 學習(三):Subscription和 Subscriber
  • Swift Combine 學習(四):操作符 Operator
  • Swift Combine 學習(五):Backpressure和 Scheduler
  • Swift Combine 學習(六):自定義 Publisher 和 Subscriber
  • Swift Combine 學習(七):實踐應用場景舉例

    文章目錄

      • 引言
      • 操作符 (`Operator`)
      • 類型擦除(Type Erasure)
      • 結語

引言

在前幾篇文章中,我們已經了解了 Combine 框架的基本概念、發布者和訂閱者的工作機制。本文將詳細介紹 Combine 中的操作符(Operator),這些操作符是處理和轉換數據流的重要工具。通過學習各類操作符的使用,我們可以更靈活地處理異步事件流,構建復雜的數據處理鏈條,從而提升應用的響應能力和性能。

操作符 (Operator)

Operator 在 Combine 中用于處理、轉換 Publisher 發出的數據。Operator 修改、過濾、組合或以其他方式操作數據流。Combine 提供了大量內置操作符,如:

  • 轉換操作符:如 mapflatMapscan,用于改變數據的形式或結構。

    • scan:用于對上游發布者發出的值進行累加計算。它接收一個初始值和一個閉包,每次上游發布者發出一個新元素時,scan 會根據閉包計算新的累加值,并將累加結果傳遞給下游。

      let publisher = [1, 2, 3, 4].publisher
      publisher.scan(0, { a, b ina+b}).sink { print($0) }
      // 1 3 6 10
      
    • map:用于對上游發布者發出的值進行轉換。它接收一個閉包,該閉包將每個從上游發布者接收到的值轉換為新的值,然后將這個新值發給下游

      let nums = [1, 2, 3, 4, 5]
      let publisher = nums.publisherpublisher.map { $0 * 10 }  // 將每個數乘以10.sink { print($0)        }// 輸出: 10 20 30 40 50
      
    • flatMap:用于將上游發布者發出的值轉換為另一個發布者,并將新的發布者的值傳遞給下游。與 map 不同,它可以對發布者進行展平,消除嵌套。

      import Combinelet publisher = [[1, 2, 3], [4, 5, 6]].publisher// 使用 flatMap 將每個數組轉換為新的發布者并展平
      let cancellable = publisher.flatMap { arr inarr.publisher // 將每個數組轉換為一個新的發布者}.sink { value inprint(value)}/* 輸出:
      1
      2
      3
      4
      5
      6
      */
      
  • 過濾操作符:包括 filtercompactMapremoveDuplicates,用于選擇性地處理某些數據。

    let numbers = ["1", "2", nil, "2", "4", "4", "5", "three", "6", "6", "6"]
    let publisher = numbers.publisherlet subscription = publisher// 使用 compactMap 將字符串轉換為整數。如果轉換失敗就過濾掉該元素.compactMap { $0.flatMap(Int.init) }// filter 過濾掉不符合條件的元素. 如過濾掉小于 3 的數.filter { $0 >= 3 }// 用 removeDuplicates 移除連續重復的元素.removeDuplicates().sink {print($0)}// 輸出: 4 5 6
    
  • 組合操作符:如 mergezipcombineLatest,用于將多個數據流合并成一個。

    • combineLatest:用于將多個發布者的最新值合成一個新的發布者。每當任何一個輸入發布者發出新值時,combineLatest 操作符會將每個發布者的最新值組合并作為元組向下游發送。
    • merge:用于將多個發布者合并為一個單一的發布者,以不確定性的順序發出所有輸入發布者的值。
    • zip:用于將兩個發布者組合成一個新的發布者,該發布者發出包含每個輸入發布者的最新值的元組。
    let numberPublisher = ["1", "2", nil].publisher.compactMap { Int($0 ?? "") }
    let letterPublisher = ["A", "B", "C"].publisher
    let extraNumberPublisher = ["10", "20", "30"].publisher.compactMap { Int($0) }// 使用 merge 合并 numberPublisher 和 extraNumberPublisher
    print("Merge Example:")
    let mergeSubscription = numberPublisher.merge(with: extraNumberPublisher).sink { value inprint("Merge received: \(value)")}// 使用 zip 將 numberPublisher 和 letterPublisher 配對
    print("\n🍎Zip Example🍎")
    let zipSubscription = numberPublisher.zip(letterPublisher).sink { number, letter inprint("Zip received: number: \(number), letter: \(letter)")}// 使用 combineLatest 將 numberPublisher 和 letterPublisher 的最新值組合
    print("\n🍎CombineLatest Example🍎")
    let combineLatestSubscription = numberPublisher.combineLatest(letterPublisher).sink { number, letter inprint("CombineLatest received: number: \(number), letter: \(letter)")}/*輸出
    Merge Example:
    Merge received: 1
    Merge received: 3
    Merge received: 10
    Merge received: 20
    Merge received: 30🍎Zip Example🍎
    Zip received: number: 1, letter: A
    Zip received: number: 3, letter: B🍎CombineLatest Example🍎
    CombineLatest received: number: 3, letter: A
    CombineLatest received: number: 3, letter: B
    CombineLatest received: number: 3, letter: C
    */
    
  • 時間相關操作符:例如 debouncethrottledelay,用于控制數據發送的時機。

    • debounce:在指定時間窗口內,如果沒有新的事件到達,才會發布最后一個事件。通常用于防止過于頻繁的觸發,比如搜索框的實時搜索。
    • throttle:在指定時間間隔內,只發布一次。如果 latesttrue,會發布時間段內的最后一個元素,false 時發布第一個元素。
    • delay:將事件的發布推遲指定時間。
    import UIKit
    import Combine
    import Foundation
    import SwiftUIclass ViewController: UIViewController {var cancellableSets: Set<AnyCancellable>?override func viewDidLoad() {super.viewDidLoad()cancellableSets = Set<AnyCancellable>()testDebounce()
    //        testThrottle()
    //        testDelay()}func testDebounce() {print("🍎 Debounce Example 🍎")let searchText = PassthroughSubject<String, Never>()searchText.debounce(for: .seconds(0.3), scheduler: DispatchQueue.main).sink { text inprint("Search request: \(text) at \(Date())")}.store(in: &cancellableSets!)// Simulate rapid input["S", "Sw", "Swi", "Swif", "Swift"].enumerated().forEach { index, text inDispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) {print("Input: \(text) at \(Date())")searchText.send(text)}}}// Throttle Examplefunc testThrottle() {print("🍎 Throttle Example 🍎")let scrollEvents = PassthroughSubject<Int, Never>()scrollEvents.throttle(for: .seconds(0.2), scheduler: DispatchQueue.main, latest: false).sink { position inprint("Handle scroll position: \(position) at \(Date())")}.store(in: &cancellableSets!)// Simulate rapid scrolling(1...5).forEach { position inprint("Scrolled to: \(position) at \(Date())")scrollEvents.send(position)}}// Delay Examplefunc testDelay() {print("🍎 Delay Example 🍎")let notifications = PassthroughSubject<String, Never>()notifications.delay(for: .seconds(1), scheduler: DispatchQueue.main).sink { message inprint("Display notification: \(message) at \(Date())")}.store(in: &cancellableSets!)print("Send notification: \(Date())")notifications.send("Operation completed")}
    }/*
    🍎 Debounce Example 🍎
    輸入: S at 2024-10-21 09:23:19 +0000
    輸入: Sw at 2024-10-21 09:23:19 +0000
    輸入: Swi at 2024-10-21 09:23:19 +0000
    輸入: Swif at 2024-10-21 09:23:19 +0000
    輸入: Swift at 2024-10-21 09:23:19 +0000
    搜索請求: Swift at 2024-10-21 09:23:19 +0000
    */
    
  • 錯誤處理操作符:如 catchretry,用于處理錯誤情況。

  • 處理多個訂閱者:例如 multicastshare

    • multicast:使用 multicast 操作符時,它會將原始的 Publisher 包裝成一個ConnectablePublisher,并且將所有訂閱者的訂閱合并為一個單一的訂閱。這樣,無論有多少個訂閱者,原始的 Publisher 都只會收到一次 receive(_:) 調用,即對每個事件只處理一次。然后,multicast 操作符會將事件分發給所有的訂閱者。

      import Combinevar cancelables: Set<AnyCancellable> = Set<AnyCancellable>()let publisher = PassthroughSubject<Int, Never>()// 不使用 multicast() 的情況
      let randomPublisher1 = publisher.map { _ in Int.random(in: 1...100) }print("Without multicast():")
      randomPublisher1.sink {print("Subscriber 1 received: \($0)")}.store(in: &cancelables)randomPublisher1.sink {print("Subscriber 2 received: \($0)")}.store(in: &cancelables)publisher.send(1)let publisher2 = PassthroughSubject<Int, Never>()// 使用 multicast() 的情況
      let randomPublisher2 = publisher2.map { _ in Int.random(in: 1...100) }.multicast(subject: PassthroughSubject<Int, Never>())print("\nWith multicast():")
      randomPublisher2.sink {print("Subscriber 1 received: \($0)")}.store(in: &cancelables)randomPublisher2.sink {print("Subscriber 2 received: \($0)")}.store(in: &cancelables)let connect = randomPublisher2.connect()
      publisher2.send(1)/*輸出:
      Without multicast():
      Subscriber 1 received: 43
      Subscriber 2 received: 39With multicast():
      Subscriber 1 received: 89
      Subscriber 2 received: 89
      */
      
    • share:它是一個自動連接的多播操作符,會在第一個訂閱者訂閱時開始發送值,并且會保持對上游發布者的訂閱直到最后一個訂閱者取消訂閱。當多個訂閱者訂閱時,所有訂閱者接收相同的輸出,而不是每次訂閱時重新觸發數據流。

      import Combinevar cancellables: Set<AnyCancellable> = Set<AnyCancellable>()let publisher = PassthroughSubject<Int, Never>()// 不使用 share() 的情況
      let randomPublisher1 = publisher.map { _ in Int.random(in: 1...100)}print("Without share():")
      randomPublisher1.sink {print("Subscriber 1 received: \($0)")}.store(in: &cancellables)randomPublisher1.sink {print("Subscriber 2 received: \($0)")}.store(in: &cancellables)publisher.send(1)let publisher2 = PassthroughSubject<Int, Never>()// 使用 share() 的情況
      let randomPublisher2 = publisher2.map { _ in Int.random(in: 1...100)}.share()print("\nWith share():")
      randomPublisher2.sink {print("Subscriber 1 received: \($0)")}.store(in: &cancellables)randomPublisher2.sink {print("Subscriber 2 received: \($0)")}.store(in: &cancellables)publisher2.send(1)/*
      輸出
      Without share():
      Subscriber 2 received: 61
      Subscriber 1 received: 62With share():
      Subscriber 2 received: 92
      Subscriber 1 received: 92
      */
      

    sharemulticast 的區別:

    • 自動連接:使用 share 時,原始 Publisher 會在第一個訂閱者訂閱時自動連接,并在最后一個訂閱者取消訂閱時自動斷開連接。
    • 無需手動連接:無需顯式調用 connect() 方法來啟動數據流,share 會自動管理連接。

我們可以使用這些操作符創建成一個鏈條。Operator 通常作為 Publisher 的擴展方法實現。

以下是一個簡化的 map 操作符示例:

extension Publishers {struct Map<Upstream: Publisher, Output>: Publisher {typealias Failure = Upstream.Failurelet upstream: Upstreamlet transform: (Upstream.Output) -> Outputfunc receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {upstream.subscribe(Subscriber(downstream: subscriber, transform: transform))}}
}extension Publisher {func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Self, T> {return Publishers.Map(upstream: self, transform: transform)}
}

類型擦除(Type Erasure)

類型擦除(type erasure)允許在不暴露具體類型的情況下,對遵循相同協議的多個類型進行統一處理。換句話說,類型擦除可以將不同類型的數據包裝成一個統一的類型,從而實現更靈活、清晰、通用的編程。

let publisher = Just(5).map { $0 * 2 }.filter { $0 > 5 }

在這個簡單的例子中 Publisher 的實際類型是 Publishers.Filter<Publishers.Map<Just<Int>, Int>, Int>。類型會變得非常復雜,特別是在使用多個操作符連接多個 Publisher 的時候。回到 Combine 中的 AnySubscriber 和 AnyPublisher,每個 Publisher 都有一個方法 eraseToAnyPublisher(),它可以返回一個 AnyPublisher 實例。就會被簡化為 AnyPublisher<Int, Never>

let publisher: AnyPublisher<Int, Never> = Just(5).map { $0 * 2 }.filter { $0 > 5 }.eraseToAnyPublisher()  // 使用 eraseToAnyPublisher 方法對 Publisher 進行類型擦除

因為是 Combine 的學習,在此不對類型擦除展開過多。

結語

操作符是 Combine 框架中強大的工具,它們使得數據流的處理和轉換變得更加靈活和高效。通過掌握操作符的使用,開發者可以創建更復雜和功能強大的數據處理邏輯。在下一篇文章中,我們將深入探討 Combine 中的 Backpressure 和 Scheduler,進一步提升對異步數據流的理解和控制調度能力。

  • Swift Combine 學習(五):Backpressure和 Scheduler

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/64929.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/64929.shtml
英文地址,請注明出處:http://en.pswp.cn/web/64929.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

時間序列預測算法---LSTM

目錄 一、前言1.1、深度學習時間序列一般是幾維數據&#xff1f;每個維度的名字是什么&#xff1f;通常代表什么含義&#xff1f;1.2、為什么機器學習/深度學習算法無法處理時間序列數據?1.3、RNN(循環神經網絡)處理時間序列數據的思路&#xff1f;1.4、RNN存在哪些問題? 二、…

leetcode題目(3)

目錄 1.加一 2.二進制求和 3.x的平方根 4.爬樓梯 5.顏色分類 6.二叉樹的中序遍歷 1.加一 https://leetcode.cn/problems/plus-one/ class Solution { public:vector<int> plusOne(vector<int>& digits) {int n digits.size();for(int i n -1;i>0;-…

快速上手LangChain(三)構建檢索增強生成(RAG)應用

文章目錄 快速上手LangChain(三)構建檢索增強生成(RAG)應用概述索引阿里嵌入模型 Embedding檢索和生成RAG應用(demo:根據我的博客主頁,分析一下我的技術棧)快速上手LangChain(三)構建檢索增強生成(RAG)應用 langchain官方文檔:https://python.langchain.ac.cn/do…

[cg] android studio 無法調試cpp問題

折騰了好久&#xff0c;native cpp庫無法調試問題&#xff0c;原因 下面的Deploy 需要選Apk from app bundle!! 另外就是指定Debug type為Dual&#xff0c;并在Symbol Directories 指定native cpp的so路徑 UE項目調試&#xff1a; 使用Android Studio調試虛幻引擎Android項目…

【Windows】powershell 設置執行策略(Execution Policy)禁止了腳本的運行

報錯信息&#xff1a; 無法加載文件 C:\Users\11726\Documents\WindowsPowerShell\profile.ps1&#xff0c;因為在此系統上禁止運行腳本。有關詳細信息&#xff0c;請參 閱 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。 所在位置 行:1 字符…

可編輯37頁PPT |“數據湖”構建汽車集團數據中臺

薦言分享&#xff1a;隨著汽車行業智能化、網聯化的快速發展&#xff0c;數據已成為車企經營決策、優化生產、整合供應鏈的核心資源。為了在激烈的市場競爭中占據先機&#xff0c;汽車集團亟需構建一個高效、可擴展的數據管理平臺&#xff0c;以實現對海量數據的收集、存儲、處…

【快速實踐】類激活圖(CAM,class activation map)可視化

類激活圖可視化&#xff1a;有助于了解一張圖像的哪一部分讓卷積神經網絡做出了最終的分類決策 對輸入圖像生成類激活熱力圖類激活熱力圖是與特定輸出類別相關的二維分數網格&#xff1a;對任何輸入圖像的每個位置都要進行計算&#xff0c;它表示每個位置對該類別的重要程度 我…

ros2 py文件間函數調用

文章目錄 寫在前面的話生成python工程包命令運行python函數命令python工程包的目錄結構目錄結構&#xff08;細節&#xff09; 報錯 1&#xff08; no module name ***&#xff09;錯誤示意 截圖終端輸出解決方法 報錯 2&#xff08; AttributeError: *** object has no attrib…

Milvus×合邦電力:向量數據庫如何提升15%電價預測精度

01. 全球能源市場化改革下的合邦電力 在全球能源轉型和市場化改革的大背景下&#xff0c;電力交易市場正逐漸成為優化資源配置、提升系統效率的關鍵平臺。電力交易通過市場化手段&#xff0c;促進了電力資源的有效分配&#xff0c;為電力行業的可持續發展提供了動力。 合邦電力…

OLED的顯示

一、I2C I2C時序&#xff1a;時鐘線SCL高電平下&#xff1a;SDA由高變低代表啟動信號&#xff0c;開始發送數據&#xff1b;SCL高電平時&#xff0c;數據穩定&#xff0c;數據可以被讀走&#xff0c;開始進行讀操作&#xff0c;SCL低電平時&#xff0c;數據發生改變&#xff1…

VMware運維效率提升50%,RVTools管理更簡單

RVTools 是一款專為 VMware 虛擬化環境量身打造的高效管理工具&#xff0c;基于 .NET 4.7.2 框架開發&#xff0c;并與 VMware vSphere Management SDK 8.0 和 CIS REST API 深度集成&#xff0c;能夠全面呈現虛擬化平臺的各項關鍵數據。該工具不僅能夠詳細列出虛擬機、CPU、內…

JS 中 json數據 與 base64、ArrayBuffer之間轉換

JS 中 json數據 與 base64、ArrayBuffer之間轉換 json 字符串進行 base64 編碼 function jsonToBase64(json) {return Buffer.from(json).toString(base64); }base64 字符串轉為 json 字符串 function base64ToJson(base64) {try {const binaryString atob(base64);const js…

介紹 C++ 中的智能指針及其應用:以 PyTorch框架自動梯度AutogradMeta為例

介紹 C 中的智能指針及其應用&#xff1a;以 AutogradMeta 為例 在 C 中&#xff0c;智能指針&#xff08;Smart Pointer&#xff09;是用于管理動態分配內存的一種工具。它們不僅自動管理內存的生命周期&#xff0c;還能幫助避免內存泄漏和野指針等問題。在深度學習框架如 Py…

python +t kinter繪制彩虹和云朵

python t kinter繪制彩虹和云朵 彩虹&#xff0c;簡稱虹&#xff0c;是氣象中的一種光學現象&#xff0c;當太陽光照射到半空中的水滴&#xff0c;光線被折射及反射&#xff0c;在天空上形成拱形的七彩光譜&#xff0c;由外圈至內圈呈紅、橙、黃、綠、藍、靛、紫七種顏色。事實…

Zabbix5.0版本(監控Nginx+PHP服務狀態信息)

目錄 1.監控Nginx服務狀態信息 &#xff08;1&#xff09;通過Nginx監控模塊&#xff0c;監控Nginx的7種狀態 &#xff08;2&#xff09;開啟Nginx狀態模塊 &#xff08;3&#xff09;配置監控項 &#xff08;4&#xff09;創建模板 &#xff08;5&#xff09;用默認鍵值…

Python入門教程 —— 字符串

字符串介紹 字符串可以理解為一段普通的文本內容,在python里,使用引號來表示一個字符串,不同的引號表示的效果會有區別。 字符串表示方式 a = "Im Tom" # 一對雙引號 b = Tom said:"I am Tom" # 一對單引號c = Tom said:"I\m Tom" # 轉義…

AcWing練習題:差

讀取四個整數 A,B,C,D&#xff0c;并計算 (AB?CD)的值。 輸入格式 輸入共四行&#xff0c;第一行包含整數 A&#xff0c;第二行包含整數 B&#xff0c;第三行包含整數 C&#xff0c;第四行包含整數 D。 輸出格式 輸出格式為 DIFERENCA X&#xff0c;其中 X 為 (AB?CD) 的…

小程序添加購物車業務邏輯

數據庫設計 DTO設計 實現步驟 1 判斷當前加入購物車中的的商品是否已經存在了 2 如果已經存在 只需要將數量加一 3 如果不存在 插入一條購物車數據 4 判斷加到本次購物車的是菜品還是套餐 Impl代碼實現 Service public class ShoppingCartServiceImpl implements Shoppin…

如何在谷歌瀏覽器中使用自定義搜索快捷方式

在數字時代&#xff0c;瀏覽器已經成為我們日常生活中不可或缺的一部分。作為最常用的瀏覽器之一&#xff0c;谷歌瀏覽器憑借其簡潔的界面和強大的功能深受用戶喜愛。本文將詳細介紹如何自定義谷歌瀏覽器的快捷工具欄&#xff0c;幫助你更高效地使用這一工具。 一、如何找到谷歌…

Python 3 與 Python 2 的主要區別

文章目錄 1. 語法與關鍵字print 函數整數除法 2. 字符串處理默認字符串類型字符串格式化 3. 輸入函數4. 迭代器和生成器range 函數map, filter, zip 5. 標準庫變化urllib 模塊configparser 模塊 6. 異常處理7. 移除的功能8. 其他重要改進數據庫操作多線程與并發類型注解 9. 總結…