在 iOS 13 及更高版本里,Apple 提供了 Combine 作为处理异步事件和数据流的响应式框架。很多开发者第一次接触它时,往往会被发布者、订阅者、操作符这些概念同时出现弄乱。本文按“先理解模型,再看代码,再落到网络请求和 SwiftUI”的顺序整理一遍,帮助你判断 Combine 到底解决什么问题、常见链路怎么写,以及它和界面状态更新是如何接上的。
Combine 核心解决什么问题
Combine 的目标,是用声明式方式处理异步事件和连续数据流。相比把回调、状态更新和错误处理分散在多个位置,Combine 倾向于把这些步骤串成一条清晰的数据处理链。
它适合的场景包括:
- 一次或多次异步数据返回,例如网络请求、表单输入、通知事件。
- 需要在接收数据后继续做转换、过滤、组合等处理。
- 希望把数据变化直接联动到界面状态,尤其是在 SwiftUI 中。
先看三个基础概念:Publisher、Subscriber 和 Operators
Publisher:数据从哪里来
Publisher 是数据的来源。它可以发送值、发送错误,并在最后进入完成状态。

Combine 内置了几类很常见的发布者:
Just:发送一个值后立即完成。PassthroughSubject:适合在运行过程中主动发送多个值,也可以手动结束。CurrentValueSubject:除了能持续发送新值,还会持有一个当前值。
Subscriber:谁来接收结果
Subscriber 是接收数据的一方,负责处理发布者送出的值、错误和完成事件。实际开发中,最常见的接收方式之一就是 sink。
Operators:中间这条链怎么加工数据
操作符负责对数据流做处理。你可以把它理解成发布者和订阅者之间的加工管道。
map:把收到的值转换成另一种形式。filter:按条件保留部分值。combineLatest:把多个发布者的最新值组合成新的结果。
这三个角色拼起来,基本就是 Combine 的最小工作模型:发布数据、沿链路处理、在末端消费。
从一个最小示例理解 Combine 的执行链
如果你只想先看 Combine 最基础的工作方式,可以从 Just 开始。这个例子展示了发布者如何发出值,操作符如何改写内容,以及订阅者如何分别处理完成和结果。
import Combine
import Foundation
// 创建一个发布者
let publisher = Just("Hello, Combine!")
// 订阅发布者
let cancellable = publisher
.map { "($0) World!" } // 使用 map 操作符转换数据
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Completed")
case .failure(let error):
print("Error: (error)")
}
}, receiveValue: { value in
print(value) // 接收转换后的值
})
这段代码可以直接拆成三步理解:
Just("Hello, Combine!")创建一个只发送一次内容的发布者。map把原始字符串转换为新的字符串。sink负责接收最终结果,并监听流程何时结束。
它很适合拿来理解 Combine 的语法结构,因为链条短、数据路径清楚,也容易看出“声明式拼接”与传统回调写法的差异。
用 Combine 处理异步网络请求
真正能体现 Combine 价值的,往往是异步任务。比如网络请求:请求发起、响应提取、JSON 解码、主线程切换和结果消费,本来会分散在多个步骤里,但在 Combine 中可以写成一条连续的数据流。

import Combine
import Foundation
// 定义一个模型
struct Post: Codable {
let id: Int
let title: String
}
// 创建一个发布者,用于网络请求
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data } // 只提取数据
.decode(type: [Post].self, decoder: JSONDecoder()) // 解码成模型
.receive(on: RunLoop.main) // 切换到主线程
.eraseToAnyPublisher() // 将发布者转换为 AnyPublisher
// 订阅发布者
let cancellable = publisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Completed")
case .failure(let error):
print("Error: (error)")
}
}, receiveValue: { posts in
print(posts) // 打印获取到的帖子
})
这条请求链分别做了什么
dataTaskPublisher(for: url):把一次网络请求包装成发布者。.map { $0.data }:从返回值中只取出响应数据。.decode(type: [Post].self, decoder: JSONDecoder()):把 JSON 解码为[Post]。.receive(on: RunLoop.main):把后续结果切换到主线程,方便更新 UI。.eraseToAnyPublisher():抹平具体发布者类型,便于统一暴露接口。
如果你之前用过回调或闭包写网络层,会发现 Combine 的优势在于:数据处理顺序和线程切换逻辑都能沿着一条链直接读下来,代码意图更集中。
Combine 如何和 SwiftUI 状态管理配合
Combine 与 SwiftUI 的结合点,核心就在状态变化自动驱动界面更新。常见做法是:在模型层使用 @Published 发布状态,在视图层通过 @StateObject 或 @ObservedObject 观察它。

import SwiftUI
import Combine
class ViewModel: ObservableObject {
@Published var posts: [Post] = []
private var cancellables = Set<AnyCancellable>()
func fetchPosts() {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: [Post].self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.replaceError(with: [])
.assign(to: &$posts) // 将结果分配给 @Published 属性
}
}
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
List(viewModel.posts) { post in
Text(post.title)
}
.onAppear {
viewModel.fetchPosts() // 视图出现时调用数据获取
}
}
}
这段 SwiftUI 示例的关键点
ViewModel遵循ObservableObject,说明它是可被界面观察的状态对象。@Published var posts用来把数组变化发布出去。assign(to: &$posts)直接把请求结果写回已发布属性。@StateObject private var viewModel = ViewModel()让视图持有并观察这个状态源。List(viewModel.posts)会随着posts更新自动刷新内容。
这也是 Combine 在 SwiftUI 中最常见的落点之一:让异步数据流不必手动一层层回传,而是自然汇入视图状态。
什么时候适合用 Combine
如果你的代码里存在明显的数据流和异步链路,比如网络请求、输入监听、状态同步、多个事件源组合,那么 Combine 往往能带来更清晰的结构。它的核心价值不是“把代码写得更短”,而是把值、错误、完成和线程切换放进同一条可追踪的处理路径里。
从本文几个例子看,可以这样快速判断:
- 只想理解语法结构,先从
Just、map、sink入手。 - 要处理真实异步任务,可以重点掌握
dataTaskPublisher、decode、receive(on:)。 - 如果项目使用 SwiftUI,那么
@Published、ObservableObject和 Combine 的组合非常值得熟悉。
总体来看,Combine 为 iOS 13 及以上版本提供了一套完整的数据流表达方式。理解发布者、订阅者和操作符之后,再回头看网络层和 SwiftUI 状态更新,整套思路会更容易串起来。









