一、Combine 是什么?
Combine 是 Apple 在 WWDC 2019 推出的声明式响应式编程框架,用于处理异步事件和数据流。它的核心思想是:将随时间变化的值(网络请求、UI 输入、定时器等)建模为事件流,通过操作符进行组合和转换。
简单说,Combine 让你用声明式的方式描述"当数据变化时做什么",而不是用嵌套闭包手动管理回调。
前置知识:Combine 大量使用 Swift 的泛型等高级特性,建议具备中级 Swift 水平再学习。
二、核心概念(三要素)
Combine 的核心围绕三个角色展开:
| 概念 | 作用 | 类比 |
|---|---|---|
| Publisher(发布者) | 事件的源头,产出值/错误/完成信号 | 报社 |
| Operator(操作符) | 处理站,转换/过滤/组合数据 | 编辑部 |
| Subscriber(订阅者) | 事件的终点,接收并消费数据 | 订户 |
数据流向:Publisher → (零个或多个 Operator) → Subscriber
2.1 Publisher(发布者)
Publisher 可以发出三种东西:
- 值(Values):你关心的数据
- 完成(Completion):
.finished表示正常结束 - 失败(Failure):
.failure(error)表示因错误终止
Publisher 在没有被订阅前什么都不会做。
常用内置 Publisher:
Just:发一个值后立即完成Future:异步产生一个结果(成功或失败)PassthroughSubject:手动发送值,不保留历史CurrentValueSubject:手动发送值,保存当前值(新订阅者立即收到最新值)@Published:属性包装器,将属性变成 Publisher(SwiftUI 中极常用)
2.2 Operator(操作符)
操作符是 Combine 强大灵活的关键,它们接收上游事件、处理后发给下游,每个操作符返回一个新的 Publisher,支持链式调用。
常用操作符分类:
| 类别 | 操作符 | 作用 |
|---|---|---|
| 转换 | map、flatMap、scan | 转换数据类型或结构 |
| 过滤 | filter、compactMap、removeDuplicates | 按条件筛选数据 |
| 合并 | merge、zip、combineLatest | 组合多个数据流 |
| 时间控制 | debounce、throttle、delay | 控制事件发送时机 |
2.3 Subscriber(订阅者)
Subscriber 消费数据。Combine 提供两个内置订阅者:
sink:最通用,接收两个闭包——处理值的receiveValue和处理完成的receiveCompletionassign:将接收到的值直接赋值给某个对象的属性(如 UI 控件的text)
三、基础用法(代码示例)
3.1 创建 Publisher 并订阅
import Combine
// 1. 创建 Publisher(发布者)
let publisher = [1, 2, 3, 4, 5].publisher
// 2. 使用 Operator 处理数据
// 3. 使用 Subscriber 订阅
let cancellable = publisher
.filter { $0 % 2 == 0 } // 只保留偶数
.map { $0 * $0 } // 平方
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("流正常结束")
case .failure(let error):
print("流因错误结束: \(error)")
}
},
receiveValue: { value in
print("接收到值: \(value)") // 输出: 4, 16
}
)
3.2 使用 Just + sink
let publisher = Just("Hello, Combine!")
publisher
.map { $0.uppercased() }
.sink { value in
print(value) // 输出: HELLO, COMBINE!
}
3.3 使用 PassthroughSubject 手动发送事件
let subject = PassthroughSubject<String, Never>()
let subscription = subject
.sink { value in
print("收到: \(value)")
}
subject.send("Hello") // 输出: 收到: Hello
subject.send("Combine") // 输出: 收到: Combine
subject.send(completion: .finished) // 发送完成信号
3.4 使用 assign 绑定到 UI
class MyViewModel {
@Published var username: String = ""
}
class MyViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
var cancellables = Set<AnyCancellable>()
let viewModel = MyViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.$username
.assign(to: \.text, on: nameLabel)
.store(in: &cancellables) // 存储订阅,防止提前释放
}
}
关键点:
store(in: &cancellables)用于管理订阅的生命周期,避免内存泄漏。
四、进阶主题
4.1 网络请求(URLSession.dataTaskPublisher)
struct Post: Decodable {
let id: Int
let title: String
}
func fetchPosts() -> AnyPublisher<[Post], Error> {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
return URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: [Post].self, decoder: JSONDecoder())
.eraseToAnyPublisher() // 类型擦除,隐藏具体 Publisher 类型
}
4.2 使用 Future 替代传统闭包回调
传统写法:
func performAsyncAction(completionHandler: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
completionHandler()
}
}
Combine 写法:
func performAsyncActionAsFuture() -> Future<Void, Never> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
promise(.success(()))
}
}
}
// 使用
performAsyncActionAsFuture()
.sink { _ in print("Future 完成") }
4.3 防抖(Debounce)—— 搜索框实战
class SearchViewModel {
@Published var searchText = ""
var cancellables = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.removeDuplicates()
.sink { text in
print("执行搜索: \(text)") // 用户停止输入 500ms 后才触发
}
.store(in: &cancellables)
}
}
4.4 合并多个 Publisher
let publisher1 = Just(1)
let publisher2 = Just(2)
let publisher3 = Just(3)
publisher1.merge(with: publisher2, publisher3)
.sink { print($0) } // 输出: 1, 2, 3(顺序不确定)
4.5 调度器(Scheduler)—— 控制线程
URLSession.shared.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global()) // 在后台线程执行网络请求
.receive(on: DispatchQueue.main) // 在主线程更新 UI
.sink(...)
五、总结
| 学习阶段 | 目标 |
|---|---|
| 入门 | 理解 Publisher、Operator、Subscriber 三要素,能写简单的 sink/assign 订阅 |
| 进阶 | 掌握常用操作符(map/filter/debounce/merge),会用 URLSession 网络请求 |
| 实战 | 结合 SwiftUI(@Published + ObservableObject)构建响应式应用,处理复杂异步场景 |
Combine 是 SwiftUI 数据驱动 UI 的基石,掌握它之后,你的异步代码将更加清晰、可维护、可组合。建议从简单的 Just 和 PassthroughSubject 开始动手练习,逐步深入到网络请求和 UI 绑定场景。

958

被折叠的 条评论
为什么被折叠?



