如何快速理解 swift Combine

Combine 是 Swift 中的一个框架,用于处理和组合异步和事件驱动的操作。
要快速理解 Combine,可以从以下几个关键概念入手:

Publisher: 发布者是一个对象,它可以发出一系列的事件,比如值、错误或完成信号;
Subscriber: 订阅者是一个对象,它可以订阅一个或多个发布者,并对发布者发出的事件做出响应;

Operators(操作符):Combine 提供了许多操作符,可以用于转换、组合和处理发布者发出的事件。例如,map 用于映射值,filter 用于过滤事件,merge 用于合并多个发布者的事件等。

Cancellable(可取消):订阅一个发布者会返回一个 Cancellable 对象,你可以使用这个对象来取消订阅,释放资源;

Subject(主题):主题是 Combine 中的一种特殊类型的发布者,既可以发送值也可以接收值。有 PassthroughSubject 和 CurrentValueSubject 等类型;

Schedulers(调度器):Combine 使用调度器来确定在哪个线程上执行操作。有一些内置的调度器,比如 DispatchQueue.main 和 DispatchQueue.global();

下面是一个简单的例子,演示了 Combine 中的一些概念:

import Combine

// 创建一个发布者,发出一系列整数
let publisher = [1, 2, 3, 4, 5].publisher

// 订阅者,处理发布者发出的事件
let subscriber = Subscribers.Sink<Int, Never>(receiveValue: { value in
    print("Received value: \(value)")
})

// 订阅发布者
let cancellable = publisher.sink(receiveValue: { value in
    print("Received value: \(value)")
})

// 取消订阅
cancellable.cancel()

一个复杂一点的例子,演示了一个 viewModel 中combine的使用:

enum ContentMode: Equatable {
        case `default`
        case text(text: String)
        case voice(path: String)
    }
    
    class ViewModel {
        /// 当前选中图片index
        @Published
        var currentPhotoIndex: Int = 0
        
        /// 文字脚本内容
        @Published
        var textScript: String = ""
        var textLanguage: String = ""
        /// 文字脚本 对应的语音音色
        @Published // TODO 类型修改为 详情中对应Model
        var speechVoice: SpeechVoiceView.Voice?
        
        /// 语音脚本内容
        @Published
        var audioScript: String = ""
        
        /// 输入内容样式Mode
        @Published
        var contentMode: ContentMode = .default
        
        /// 是否能够生成
        @Published
        var enableGenerate: Bool = false
        
        /// 详情数据
        @Published
        var detail: DetailModel?
        
        /// 用户图
        @Published
        var userPhotos: [PTStylePhoto] = []
        
        /// Demo图
        @Published
        var demoPhotos: [PTStylePhoto] = []
        
        /// 用户图+Demo图
        @Published
        var allPhotos: [PTStylePhoto] = []
        
        // 是否结束播放
        @Published
        var isEndVideoPlay: Bool = false
        
        // 是否结束播放
        @Published
        var isEndAudioPlay: Bool = false
        
        let itemWidth = ScreenWidth - 16.0 * 4
        
        var contentModeName: String {
            switch contentMode {
            case .default:
                return ""
            case .text(_):
                return "text"
            case .voice(_):
                return "voice"
            }
        }
        
        
        var cancellables = Set<AnyCancellable>()
        
        init() {
            initialize()
        }
        
        private func initialize() {
            $demoPhotos
                .sink { [weak self] in
                    guard let self else { return }
                    allPhotos = userPhotos + $0
                }.store(in: &cancellables)
            
            // 查询用户图
            PTUserPhotoManager.manager.$allUserPhotos
                .map({ $0.map { PTStylePhoto(userPhoto: $0) } })
                .sink { [weak self] value in
                    guard let self else { return }
                    userPhotos = value
                }
                .store(in: &cancellables)
            //
            PTUserPhotoManager.manager.queryUserPhoto()
            
            // 请求详情数据
            PhotoTalkAPI.shared.publisher(.detail, type: DetailModel.self)
                .loading("loading...")
                .map { $0.result }
                .retry(2)
                .sink(receiveCompletion: { error in
                    
                }, receiveValue: { [weak self] value in
                    guard let self else { return }
                    detail = value
                    if let demoList = value?.demoList {
                        demoPhotos = demoList.map({
                            PTStylePhoto(demo: $0)
                        })
                    }
                })
                .store(in: &cancellables)
            
            $userPhotos.combineLatest($demoPhotos)
                .dropFirst()
                .map { $0 + $1 }
                .assign(to: &$allPhotos)
            

            // 输入内容状态
            let textPublisher = $textScript.filter({
                !$0.isEmpty
            }).map {
                ContentMode.text(text: $0)
            }.removeDuplicates()
            
            let audioPublisher = $audioScript.filter({
                !$0.isEmpty
            }).map {
                ContentMode.voice(path: $0)
            }.removeDuplicates()
            
            textPublisher
                .merge(with: audioPublisher)
                .assign(to: &$contentMode)
            
            
            // 能否合成状态
            $contentMode.sink { [unowned self] x in
                switch x {
                case .default:
                    enableGenerate = false
                case let .text(text):
                    enableGenerate = !text.isEmpty
                case let .voice(path):
                    enableGenerate = !path.isEmpty
                }
            }.store(in: &cancellables)
            
        }

    }

这只是 Combine 的入门,你可以通过深入学习每个概念、查阅文档以及进行实际的应用来更全面地理解 Combine。 Combine 的强大之处在于它提供了一种声明式的方式来处理异步操作,使得代码更易于理解和维护。

想详细了解Combine,请转以下链接:
https://icodesign.me/posts/swift-combine/

Combine中的操作符,逐个举例说明

Combine 框架中提供了许多强大的操作符,用于处理和组合异步事件流。下面是一些常用的 Combine 操作符,逐个举例说明它们的用法:

map 操作符:用于映射每个元素。

let publisher = [1, 2, 3].publisher
_ = publisher.map { $0 * 2 }
          .sink { value in
              print(value) // 输出:2, 4, 6
          }

filter 操作符:用于过滤元素。

let publisher = [1, 2, 3, 4, 5].publisher
_ = publisher.filter { $0 % 2 == 0 }
          .sink { value in
              print(value) // 输出:2, 4
          }

combineLatest 操作符:用于合并多个发布者的最新元素。

let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()

_ = publisher1.combineLatest(publisher2)
           .sink { value in
               print(value)
           }

publisher1.send(1)
publisher2.send("A")
// 输出:(1, "A")

merge 操作符:
用于合并多个发布者的元素。

let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<Int, Never>()

_ = Publishers.Merge(publisher1, publisher2)
          .sink { value in
              print(value)
          }

publisher1.send(1)
publisher2.send(2)
// 输出:1, 2

flatMap 操作符:
用于将每个元素映射到一个新的发布者,然后将这些发布者的元素合并成一个新的发布者。

let publisher = [1, 2, 3].publisher
_ = publisher.flatMap { value in
             Just(value * 2)
          }
          .sink { value in
              print(value) // 输出:2, 4, 6
          }

scan 操作符:
用于对元素进行累积操作。

let publisher = [1, 2, 3].publisher
_ = publisher.scan(0) { accumulator, value in
             accumulator + value
          }
          .sink { value in
              print(value) // 输出:1, 3, 6
          }

这只是一小部分 Combine 操作符,它们提供了强大的工具,用于处理和组合异步事件流。
根据具体的业务需求,你可以组合使用这些操作符来构建复杂的异步数据处理流程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,431评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,637评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,555评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,900评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,629评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,976评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,976评论 3 448
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,139评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,686评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,411评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,641评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,129评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,820评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,233评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,567评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,362评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,604评论 2 380

推荐阅读更多精彩内容