selector

如何快速理解 swift Combine

2023-11-20  本文已影响0人  大成小栈

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 操作符,它们提供了强大的工具,用于处理和组合异步事件流。
根据具体的业务需求,你可以组合使用这些操作符来构建复杂的异步数据处理流程。

上一篇下一篇

猜你喜欢

热点阅读