Combine 框架详解

什么是Combine

Combine 是 Apple 推出的响应式框架,它把异步事件抽象为数据流,开发者可以通过链式语法组合处理各种异步和时间驱动事件(如 UI 交互、网络、定时器、通知等)。如熟悉RXSwift 那么我理解Combine就是官方出的来平衡RX的。

Combine 核心概念

Publisher

Publisher 是数据发布者,所有的异步事件流都由它产生。

let publisher = Just("Hello Combine")
Subscriber

Subscriber 订阅 Publisher,并处理事件。

publisher
    .sink { value in
        print(value)
    }

Subject

Subject 既是 Publisher 又是 Subscriber,可用于手动控制事件流。

let subject = PassthroughSubject<String, Never>()
subject
    .sink { print("received: \($0)") }

subject.send("hello")

Operator

对数据流进行转换、过滤、合并等处理。

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

Scheduler

决定在哪个线程上执行

Just("run on background")
    .subscribe(on: DispatchQueue.global())
    .receive(on: RunLoop.main)
    .sink { print($0) }

Cancellable

用于管理订阅生命周期

let cancellable = Just("combine")
    .sink { print($0) }
cancellable.cancel()

Combine 基础示例

例:绑定文本输入框

import Combine
import UIKit

class ViewModel {
    @Published var text: String = ""
    var cancellables = Set<AnyCancellable>()

    init() {
        $text
            .sink { print("输入内容:\($0)") }
            .store(in: &cancellables)
    }
}

Combine 与 SwiftUI 联动

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        TextField("请输入内容", text: $viewModel.text)
            .padding()
    }
}

Combine 网络请求封装

struct Post: Decodable {
    let title: String
}

func fetchPosts() -> AnyPublisher<[Post], Error> {
    URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
        .map(\.data)
        .decode(type: [Post].self, decoder: JSONDecoder())
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
}

调用

fetchPosts()
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("错误:\(error)")
        }
    }, receiveValue: { posts in
        print("接收数据:\(posts.count)")
    })
    .store(in: &cancellables)

Combine 与异步操作

监听通知

NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
    .sink { _ in print("进入后台") }
    .store(in: &cancellables)

定时器

Timer.publish(every: 1.0, on: .main, in: .common)
    .autoconnect()
    .sink { print("当前时间:\($0)") }
    .store(in: &cancellables)

@Published、@StateObject 与 Combine

class MyViewModel: ObservableObject {
    @Published var count = 0
}

  • @Published:属性变更会发出 Publisher。
  • @StateObject:在 SwiftUI 中持有 ViewModel 生命周期。
  • @ObservedObject:由外部传入。

Combine 高阶用法解析

  • merge:合并多个发布者成为一个;
  • combineLatest / zip:将多个流的最新值组合;
  • Subject 类型:PassthroughSubject, CurrentValueSubject 用于手动广播事件;
  • Future / AnyPublisher:桥接异步任务,例如封装网络请求或 Task 执行结果。
let a = PassthroughSubject<Int, Never>()
let b = PassthroughSubject<String, Never>()

a.combineLatest(b)
  .sink { aVal, bVal in
    print("Combined:", aVal, bVal)
  }

a.send(1)
b.send("Hello")

let apiA = URLSession.shared.dataTaskPublisher(for: urlA).map(\.data)
let apiB = URLSession.shared.dataTaskPublisher(for: urlB).map(\.data)

Publishers.Zip(apiA, apiB)
    .sink(receiveCompletion: { print($0) },
          receiveValue: { dataA, dataB in
              print("Combined Response")
          })
    .store(in: &cancellables)

Combine + SwiftUI 结合

Combine 是 SwiftUI 的灵魂伴侣,主要通过 @Published 和 ObservableObject 驱动界面更新。

ViewModel 示例

class SearchViewModel: ObservableObject {
    @Published var keyword: String = ""
    @Published var results: [String] = []

    private var cancellables = Set<AnyCancellable>()

    init() {
        $keyword
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.search(text)
            }
            .store(in: &cancellables)
    }

    func search(_ text: String) {
        results = ["Result 1 for \(text)", "Result 2 for \(text)"]
    }
}

配合 View 使用

struct SearchView: View {
    @StateObject var viewModel = SearchViewModel()

    var body: some View {
        VStack {
            TextField("Search", text: $viewModel.keyword)
                .textFieldStyle(RoundedBorderTextFieldStyle())
            List(viewModel.results, id: \.self) {
                Text($0)
            }
        }.padding()
    }
}

Combine 与 RxSwift 对比

功能 / 概念 RxSwift Combine
基础单位 Observable / Single / Maybe / Completable Publisher
订阅者 Observer / Subscriber Subscriber / Sink / Assign
取消订阅 DisposeBag / Disposable AnyCancellable
调度器/线程切换 observeOn, subscribeOn, MainScheduler.instance receive(on:), subscribe(on:), DispatchQueue.main
响应式操作符 map, flatMap, filter, merge, combineLatest 同名操作符也基本支持
错误处理 catchError, retry catch, retry
绑定 UI rx.tap, rx.text(通过 RxCocoa) 使用 @Published + assign(to:on:) 或 CombineCocoa
热/冷信号 PublishSubject, BehaviorSubject, ReplaySubject PassthroughSubject, CurrentValueSubject
背压控制 RxSwift 不支持背压(手动控制) Combine 支持 Demand 背压
单值信号支持 Single, Maybe, Completable 没有内建,需手动模拟(如 .first() 等)
扩展性 社区活跃,有大量第三方库(如 RxCocoa, RxDataSources) Apple 官方维护,生态正在发展中
KVO 支持 rx.observe(...) 自动支持,使用 @Published, ObservableObject

技术选型建议

在实际项目中,如何选择响应式技术栈,应根据项目的最低支持版本和团队熟悉度综合考虑:

如果项目最低支持 iOS 13 及以上,可用 SwiftUI + Combine + MVVM 架构。SwiftUI 与 Combine 天生集成,语法简洁、声明式风格强,能够大幅提升 UI 和状态管理的同步效率。

如果项目最低支持 iOS 12 或以下,可采用 UIKit + RxSwift + MVVM。RxSwift 是一套成熟、功能全面的响应式编程库,在 UIKit 项目中表现稳定,也有丰富的社区资源支持。

函数响应式编程(FRP)优缺点分析

函数响应式编程(FRP)在 iOS 开发中越来越受欢迎,但也伴随着一些权衡:

优点:

状态流转清晰:通过 Publisher 和 Subscriber 解耦数据源与消费者,数据流动更直观。
链式组合能力强:提供如 map, flatMap, filter, combineLatest 等高阶函数,可高效处理复杂的异步链路和事件流。
可复用性强:将 UI 逻辑与业务逻辑解耦,更易进行单元测试与模块复用。

缺点:

学习曲线陡峭:需要理解观察者模式、流的概念、背压、调度等高级抽象。
调试较困难:错误传播机制复杂,常常需要引入调试工具(如 RxSwift 的 RxDebug, Combine 的 .handleEvents)。
内存管理容易踩坑:Combine 在使用 sink, assign 时需注意 AnyCancellable 生命周期管理;RxSwift 则依赖 DisposeBag。

Swift Concurrency 与 Combine 的关系与比较

随着 Swift 5.5 引入 Swift Concurrency(即 async/await + Task + Actor),Apple 提供了另一套现代化的异步编程方案。

Swift Concurrency 优势:
  • 更符合直觉的同步/异步表达形式(async/await)
  • 更强的类型安全和编译期检查
  • Actor 模型天然解决线程安全问题
它和 Combine/RxSwift 的差异:
特性 Combine / RxSwift Swift Concurrency
异步风格 流式,声明式 命令式 async/await
多个值流 支持 Publisher(可无限发出值) 需用 AsyncStream 手动模拟
错误处理 内置 .catch, .retry 等操作符 基于 do-catch 语法处理
背压控制 有明确机制(如 removeDuplicates, debounce 无自动背压,需要自己控制
UI 绑定 SwiftUI 完美集成 Combine 利用 @MainActor + ObservableObject 辅助

综合建议:

如果你项目中已有大量 Combine 代码,建议继续使用;SwiftUI + Combine 配合紧密,维护也容易。
新项目如果逻辑更偏向任务驱动(如调用 API、加载数据),并希望简化异步流程,可尝试 Swift Concurrency。
如果你想结合使用,也可用 Combine 构建流式处理逻辑,最终在 sink 中调用 async 函数,二者并不冲突。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容