什么是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 函数,二者并不冲突。