Swift中的Combine
目录
前言
一、Combine 的核心概念
1.Publisher(发布者)
2.Subscriber(订阅者)
3.Operator(操作符)
4.Cancellable
二、使Combine 的基本用法
1.Publisher(发布者)
1.Just
2.Future
3.Deferred
4.Empty
5.Fail
6.Timer.TimerPublisher
7.NotificationCenter.Publisher
8.URLSession.DataTaskPublisher
9.Publishers.Sequence
10.PassthroughSubject
11.CurrentValueSubject
2. Subscriber(订阅者)
1.sink
2.assign
3.自定义订阅者 (Subscriber)
3.Operator(操作符)
1.map
2.filter
3.removeDuplicates
4.combineLatest
5.merge
6.debounce
7.throttle
8.catch
4. Cancelable(操作符)
1.Cancellable 的用途和作用
2.Cancellable 的实现方式
1.基础用法
2.管理多个 Cancellable 的集合
3.使用AnyCancellable自动取消
4.常见使用场景
1.在UIViewController 中使用 Cancellable
2.Cancellable 总结
前言
Combine 是苹果在iOS 13版本中所发布的响应式编程框架。
该框架旨在管理各种异步操作以及数据流。
借助该框架, 开发者能够将各种异步操作整合为统一的数据流, 并对其进行有效管理。
在网络上搜索有关Combine的教程后发现有两种途径:一是从苹果官方翻译过来的文档;另一种则是通过SwiftUI与Combine直接进行绑定操作来实现功能。这种方法无疑使得尚未熟悉SwiftUI的同学在学习成本方面面临了一定挑战。今天特意整理了自己的理解希望对刚开始接触Combine的学习者们有所帮助
创作不易,您的鼓励和支持是作者创作的不懈动力。
一、Combine 的核心概念
其中的核心概念包括发布机构、订阅平台、操作主体以及可终止性特征。
1.Publisher(发布者)
Publisher是数据的来源,它可以发布一系列的值或者失败的事件。
Publisher 有两种不同的处理路径:一种是成功处理并提交成功事件;另一种是直接提交失败事件。
常见使用的 Publisher 类型包括 Just, Future, PassthroughSubject, 和 CurrentValueSubject 等
2.Subscriber(订阅者)
**** Subscriber 订阅 Publisher 并接收数据或完成事件。
Combine包含了内置的Subscribers.Sink和Assign,并支持自定义订阅者的创建。
3.Operator(操作符)
该类功能可对 Publisher 提供的数据执行转换、筛选以及整合等多种处理操作,并与 RxSwift 的功能实现相匹配
常用的操作符有 map、filter、flatMap、removeDuplicates、merge、zip 等。
4.Cancellable
Cancellable 是一个协议,表示订阅可以被取消,从而终止数据流。
通过调用 cancel() 方法可以发送取消订阅请求,在实际应用中通常会将订阅项存储在 Set
二、使Combine 的基本用法
1.Publisher(发布者)
1.Just
Just用于立即发布一个特定值,并立即完成。
Just有以下使用场景:
1.调试与测试:在开发过程中临时显示某些静态数据,方便调试数据管道
2.占位符数据:在数据加载阶段中使用静态文本信息作为提示或占位符有助于优化用户体验体验。
- 初始化静态状态:例如用于实现固定的消息(如欢迎消息或错误提示)的单次显示效果。
比如在以下案例中

图1.Just的实例
2.Future
Future 是 Combine 中的一个发布者,在未来某个时间点进行异步操作并返回一个值(或出现失败状态)。这种机制常用于将复杂的异步操作进行封装处理,并以网络请求、文件读取等为例进行具体实现
在以下示例中, 通过按下按钮发送模拟化的网络请求,并将结果界面展示出来.
import UIKit
import Combine
class FutureDemoViewController: UIViewController {
private var cancellables = Set<AnyCancellable>() // 用于管理订阅
private let resultLabel = UILabel() // 用于显示请求结果
private let requestButton = UIButton(type: .system) // 请求按钮
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
view.backgroundColor = .white
self.title = "Future 使用示例"
// 配置按钮
requestButton.setTitle("发送请求", for: .normal)
requestButton.addTarget(self, action: #selector(makeRequest), for: .touchUpInside)
// 配置结果标签
resultLabel.text = "等待请求结果..."
resultLabel.textAlignment = .center
// 添加子视图并布局
view.addSubview(resultLabel)
view.addSubview(requestButton)
resultLabel.translatesAutoresizingMaskIntoConstraints = false
requestButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
resultLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
resultLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
requestButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
requestButton.topAnchor.constraint(equalTo: resultLabel.bottomAnchor, constant: 20)
])
}
@objc private func makeRequest() {
fetchDataFromServer()
.receive(on: DispatchQueue.main) // 确保在主线程更新 UI
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("请求完成")
case .failure(let error):
self.resultLabel.text = "请求失败: \(error.localizedDescription)"
}
}, receiveValue: { value in
self.resultLabel.text = "请求结果: \(value)"
})
.store(in: &cancellables)
}
/// 模拟从服务器获取数据的 Future
private func fetchDataFromServer() -> Future<String, Error> {
return Future { promise in
print("开始请求数据...")
DispatchQueue.global().asyncAfter(deadline: .now() + 2) { // 模拟网络延迟
let success = Bool.random() // 随机成功或失败
if success {
promise(.success("请求成功:返回的数据"))
} else {
promise(.failure(NSError(domain: "网络错误", code: -1, userInfo: nil)))
}
}
}
}
}
Swift

3.Deferred
Deferred 被视为 Combine 中的一个发布者,并主要负责实现延迟创建与执行发布者的功能。当订阅发生时,系统会自动创建新的发布者实例。这种机制特别适用于动态生成发布者或仅在订阅阶段初始化发布者的场景,在这些情况下具有显著优势。
我们来搭建一个示例,在按钮点击事件中实现延迟执行,并返回处理结果。通过Deferred实现网络请求模拟
import UIKit
import Combine
class DeferredDemoViewController: UIViewController {
private var cancellables = Set<AnyCancellable>() // 管理订阅
private let resultLabel = UILabel() // 显示请求结果
private let requestButton = UIButton(type: .system) // 请求按钮
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
view.backgroundColor = .white
self.title = "Deferred 使用示例"
// 设置按钮
requestButton.setTitle("发送请求", for: .normal)
requestButton.addTarget(self, action: #selector(makeRequest), for: .touchUpInside)
// 设置结果标签
resultLabel.text = "等待请求结果..."
resultLabel.textAlignment = .center
// 添加视图并设置布局
view.addSubview(resultLabel)
view.addSubview(requestButton)
resultLabel.translatesAutoresizingMaskIntoConstraints = false
requestButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
resultLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
resultLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
requestButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
requestButton.topAnchor.constraint(equalTo: resultLabel.bottomAnchor, constant: 20)
])
}
@objc private func makeRequest() {
createDeferredPublisher()
.receive(on: DispatchQueue.main) // 主线程更新 UI
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("请求完成")
case .failure(let error):
self.resultLabel.text = "请求失败: \(error.localizedDescription)"
}
}, receiveValue: { value in
self.resultLabel.text = "请求结果: \(value)"
})
.store(in: &cancellables)
}
/// 使用 Deferred 创建一个新的延迟发布者
private func createDeferredPublisher() -> AnyPublisher<String, Error> {
return Deferred {
Future { promise in
print("开始请求数据...")
DispatchQueue.global().asyncAfter(deadline: .now() + 2) { // 模拟网络延迟
let success = Bool.random() // 随机成功或失败
if success {
promise(.success("请求成功:返回的数据"))
} else {
promise(.failure(NSError(domain: "网络错误", code: -1, userInfo: nil)))
}
}
}
}
.eraseToAnyPublisher()
}
}
Swift

4.Empty
在Combine组件中有一个名为Empty的对象,默认情况下不会发送任何数据并立即终止流程。它通常应用于无需传递数据的情境或者仅作为流程终结的一种标志(成功或失败)。以下是一个实例展示如何使用Empty来表示无数据的情形。
5.Fail
Fail用于发布错误,主要用于模拟失败场景。
非常适合测试错误处理。
let errorPub = Fail<Int, NSError>(error: NSError(domain: "Error", code: -1, userInfo: nil))
errorPub.sink(receiveCompletion: { print($0) }, receiveValue: { print("Received") })
// 输出错误详情
6.Timer.TimerPublisher
**** 生成定时事件流的发布者,常用于需要定时更新的 UI 场景。
可以指定时间间隔来发布数据。
let timerPublisher be the schedule created by Timer每隔1秒在主线程内持续运行;
let cancellable be a self-automated connection of timerPublisher;
The pipeline connector will receive time values and print the current time each time it is passed;
7.NotificationCenter.Publisher
**** 从系统或自定义通知发布数据。
可以监听系统通知或应用内自定义通知。
let notificationPublisher = 社区中心.default publisher when UIApplication entered background
notificationPublisher.sink { _ in print "App entered background" }
8.URLSession.DataTaskPublisher
**** 专用于执行网络请求,发布请求结果或错误。
适用于网络请求的响应式处理。
创建并返回一个数据任务发布器实例。
该实例将被用来处理指定的URL。
调用数据任务发布器共享实例来处理给定的URL。
然后将映射操作应用到数据流上。
接着使用JSON解码器将数据解码为Post类型。
最后将解码后的结果通过管道传递给打印操作以完成处理。
9.Publishers.Sequence
**** 将数组或集合等序列转换为发布者,顺序发布每个元素。
适合在数据管道中逐个发布已有的数组数据。
const seriesPublisher = Publishers.Series(sequence: [5.0, 4.0, 3.0, 2.0, 1.0])
seriesPublisher.sink(receiveCompletion: { print(0) }, receiveValue: { print(1) })
// 输出: 完成中,请稍候...54321已完成
10.PassthroughSubject
**** 用于创建手动控制的数据流,可以动态发布数据。
适用于需要手动触发事件的场景,比如按钮点击。
let throughLink = ThroughLink<String, Never>()
throughLink.sink(receiveValue: { print($0) })
throughLink.send("Hello")
throughLink.send("World")
// 输出: Hello, World
11.CurrentValueSubject
**** 与 PassthroughSubject 类似,但保存并发布最新的值。
适用于需要随时读取最新值的场景(类似于变量)。
定义一个名为 currentValueSubject的变量,并将其赋值为一个CurrentValueSubject<int, never>类型的实例,并传入参数10。
然后订阅该实例以获取当前值,并在订阅过程中打印出"Current value: \($0)"的信息。
接着分别向该实例发送数据20和30。
最终会输出三次结果:"Current value: 10" "Current value: 20" "Current value: 30"
2. Subscriber(订阅者)
1.sink
sink 是 Combine 中最主要的订阅者之一,在闭包环境中它提供了灵活的数据订阅机制以及对成功与失败事件的支持方案;其常见的两种实现形式分别为:
1.接受数据与完成事件:同时订阅值和完成事件.
2.只接受数据**:** 仅订阅值更新,适合只需要处理数据而忽略完成状态的场景.
import Combine
let publisher = Just("Hello, Combine!")
let cancellable = publisher.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error)")
}
},
receiveValue: { value in
print("接收到值:\(value)")
}
)
在该示例中,在捕获来自 Just 发布机构发出的文本信息 'Hello, Combine!' 后,在控制台处进行了显示。
2.assign
assign 作为一种独特的订阅者,在实际应用中负责将发布者的值直接赋值给目标对象的属性。这种机制常见于UI更新场景中,在SwiftUI或iOS开发框架中能够实现对UI组件的数据动态绑定
import Combine
class ExampleViewModel: ObservableObject {
@Published var text: String = ""
}let viewModel = ExampleViewModel()
let publisher = Just("Updated Text")let cancellable = publisher.assign(to: .text, on: viewModel)
print(viewModel.text) // 输出: Updated Text
在这个示例中, assign 负责处理 Just 发布者的字符串, 直接将其赋值给 viewModel 的 text 属性. 从而简化了数据流向属性的赋值流程.
3.自定义订阅者 (Subscriber)
通过Combine机制我们可以轻松创建自定义订阅者以便完全掌控数据接收并完成相应的处理流程
import Combine
where:
struct CustomSubscriber: Subscriber {
typealias Input = String
typealias Failure = Neverfunc receive(subscription: Subscription) {
print("订阅开始") // 调用订阅方法
subscription.request(.max(1))! // 请求最多一个数据}
func receive(_ input: String) -> .demand {
print("接收到数据:\(input)") // 接收输入数据
return .none // 完成订阅请求
}func receive(completion: Subscribers.Completion
) {
print("订阅完成") // 完成订阅操作
}
}
let publisher = Just("Hello, Custom Subscriber!")
let customSubscriber = CustomSubscriber()
publisher.subscribe(customSubscriber)
在这一实例中,默认订阅者通过遵循Subscriber 协议中的三个核心功能来实现其功能。响应订阅请求:发起请求获取所需数据项。接收发布数据:响应发布的数据。处理完成状态:通过调用receive(completion:)方法来处理完成状态。
3.Operator(操作符)
1.map
该函数用于将每个元素映射到另一个数据类型,并特别适用于处理较为简单的映射场景。
import Combine
let publisher = [1, 2, 3, 4, 5].publisher
let cancellable = publisher
.map { $0 * 2 } // 每个值乘以 2
.sink { value in
print(value) // 输出:2, 4, 6, 8, 10
}
2.filter
filter 用于滤波处理满足特定条件的数据项,在数据处理过程中仅输出符合指定要求的部分内容,并适用于满足特定筛选需求的情况。
导入Combine库
定义 publisher 变量为 [1,2,3,4,5] 的 publisher 属性
将 cancellable 设定为 publisher 的副本
通过 .filter 过滤出偶数值 // 筛选出偶数值
.sink 接收值并打印输出结果:$1 // 输出结果:2 和4
3.removeDuplicates
removeDuplicates 用于删除连续重复的数值,并仅保留与前一个数值不同的元素,在处理去重问题时非常有效。
import Combine
let publisher = [1, 2, 2, 3, 3, 3, 4].publisher
let cancellable = publisher
.removeDuplicates()
.sink { value in
print(value) // 输出:1, 2, 3, 4
}
4.combineLatest
该命令主要用于整合来自两个发布者的最新数据,在任一发布者提交新信息时,所有提交都会采用当前最新的整合结果作为输出,并特别适用于大规模并行数据流的同步与协调处理。
import Combine
let publisher1 = PassthroughSubject<String, Never>()
let publisher2 = PassthroughSubject<Int, Never>()let cancellable = publisher1
.combineLatest(publisher2)
.sink { value in
print("组合的最新值:\(value)") // 输出最新组合值
}publisher1.send("A")
publisher2.send(1) // 输出: ("A", 1)
publisher1.send("B") // 输出: ("B", 1)
5.merge
**** combine 将来自多个发布者的输出数据进行整合,只要有任何一个发布机构发送数据信息,则整合后的统一发布机构会转发这些数据信息。特别适用于需要将多路数据流量汇聚到单一来源以实现高效传输的应用环境。
导入Combine库
通过关键字指定 publisher1 和 publisher2 分别为列表 [1, 2, 3] 和 [4, 5, 6] 的 publisher
将 cancellable 定义为 publisher1 并通过 merge 方法与 publisher2 进行合并操作
然后通过 sink 方法实现数据的接收与处理功能
在接收操作中打印输出结果值
输出结果为:1, 2, 3, 4, 5, 6
6.debounce
在一段期间内不再接收新的输入值时才会输出最后一个值,并广泛应用于处理大量连续发生的事件(例如用户输入),以避免过频繁地被调用。
import Combine
import Foundationlet subject = PassthroughSubject<String, Never>()
let cancellable = subject
.debounce(for: .seconds(1), scheduler: DispatchQueue.main) // 1秒内无新值时才发送
.sink { value in
print(value)
}subject.send("A")
subject.send("B") // 快速连续的值 "A" 和 "B",只会输出 "B"
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
subject.send("C") // 1.5秒后发送 "C",会立即输出
}
7.throttle
一种控制机制类似于一种去抖动机制,在不考虑这一时间段内是否有新值产生的情况下仍会发送最新的数值信息,并特别适用于控制数据流频率的情形
8.catch
通过捕获机制实现异常处理功能,在故障发生时可利用备用发布者完成后续操作,并特别适用于后退策略的设计
导入Combine库
创建一个错误处理失败的对象publisher
将其标记为可取消canceller
捕获错误并返回备用值Just("备用值")
将结果传递给print函数
4. Cancelable(操作符)
在 Swift 的 Combine 框架中, Cancellable 是一个关键性的协议,负责管理并取消订阅.每当我们在框架中订阅了一个 Publisher 时,Combine 会返回满足该协议的一个实例.通过该实例可以在适当的时候实现取消订阅,从而有效停止数据流的处理,避免资源浪费与内存泄漏.
1.Cancellable 的用途和作用
在Combine框架中构建信息流时,默认情况下会基于发布者(Publisher)与订阅者(Subscriber)之间的订阅关系进行管理。当需要限制订阅者的数据接收范围时,在某些场景下可以选择仅允许其从发布者获取限定时间段内的信息流序列
1.用户离开当前页面,数据不再需要更新
2.网络请求超时或任务已经完成
3.用户主动取消操作
当下
2.Cancellable 的实现方式
所有遵循Cancellable协议的实体都具有名为cancel()的方法以实现取消订阅功能。以下是一些常见的用法及示例:
1.基础用法
创建发布者并订阅它,然后使用 Cancellable 来管理订阅状态。
import Combine
let publisher = ["Hello", "Combine", "Framework"].publisher
// 订阅并获取 Cancellable 实例
let subscription: Cancellable = publisher.sink { value in
print("Received value: \(value)")
}
// 取消订阅
subscription.cancel()
以上述案例为例,在subscription.cancel()被调用之后,发布者将停止向订阅者发送数据
2.管理多个 Cancellable 的集合
在实际项目中常见的是存在多个数据流的订阅情况。为此我们可以采用AnyCancellable以及Set
import Combine
var cancellables = Set
() let publisher1 = ["A", "B", "C"].publisher
let publisher2 = [1, 2, 3].publisher
publisher1
.sink { value in
print("Publisher1 received: \(value)")
}
.store(in: &cancellables) // 自动存储到 cancellables 集合
publisher2
.sink { value in
print("Publisher2 received: \(value)")
}
.store(in: &cancellables) // 自动存储到 cancellables 集合
// 在适当时机清空集合,取消所有订阅
cancellables.removeAll()
在上述代码中,所有包含在 cancelable 集合体中的 Cancellable 都会经历 removeAll 的取消。
3.使用AnyCancellable自动取消
AnyCancellable 属于一种特定的 Cancellable 类型,在超出其范围时会自动取消订阅。因此,在不手动调用 cancel() 的情况下,默认会等到生命周期结束才会取消订阅。
import Combine
let publisher = ["Hello", "World"].publisher
let cancellable = publisher.sink { value in
print("Received value: \(value)")
}
do {
let anyCancellable = AnyCancellable {
print("Subscription is cancelled")
}
//
anyCancellable超出作用域后会自动执行 cancel}
4.常见使用场景
1.在UIViewController 中使用 Cancellable
通过Set
import Combine
import UIKit
class MyViewController: UIViewController {
private var cancellables = Set
() override func viewDidLoad() {
super.viewDidLoad()
let publisher = ["Combine", "is", "powerful"].publisher
publisher
.sink { value in
print("Received value: \(value)")
}
.store(in: &cancellables) // 自动管理取消
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
cancellables.removeAll() // 页面消失时取消所有订阅
}
}
在这一实例中,在页面退出时cancellables被清除,并且所有订阅将被删除以避免资源使用上的浪费
2.Cancellable 总结
1. 管理数据流 :在 Combine 中,Cancellable 是管理数据流的重要工具。
2. 避免资源泄漏 :当不再需要数据流时,可以通过 cancel() 取消订阅,释放资源。
3. 集合操作 :Set[AnyCancellable]能够高效地管理多个订阅,并特别适合用于Aancillary system等复杂的系统。
AnyCancellable 具备自动管理作用域内撤回的能力,在临时订阅的情景下非常适用。
