iOS Combine 使用教程
1. 什么是 Combine?
Combine 是 Apple 在 iOS 13 引入的响应式编程框架,旨在处理异步事件流。它可以用于处理 UI 事件、网络请求、定时器、通知等,替代传统的回调、闭包和 NotificationCenter。类似iOS的第三方框架RxSwift,Java中的RxJava
2. Combine 核心概念
2.1. Publisher(发布者)
Publisher 负责发布数据流,常见的 Publisher 包括:
• Just(value):发送一个固定值,然后完成。
• Future:执行异步任务,并在未来返回值。
• PassthroughSubject 和 CurrentValueSubject:手动发送数据。
• URLSession.DataTaskPublisher:用于网络请求。
2.2. Subscriber(订阅者)
Subscriber 负责订阅 Publisher,并处理数据,常见的有:
• sink(receiveValue:) 直接处理 Publisher 发送的数据。
• assign(to:on:) 绑定数据到某个对象的属性。
2.3. Operator(操作符)
Operator 用于转换数据流,例如:
• map(_:):数据映射。
• filter(_:):数据过滤。
• flatMap(_:):扁平化数据。
• combineLatest、merge、zip 处理多个数据流。
2.4. Cancellable(取消订阅)
• store(in:):存储 AnyCancellable 以防止订阅被回收。
3. Combine 基础使用之Publisher(发布者)
3.1. Just
Just用于立即发布一个特定值,并立即完成(可以先发后收)
//1.Just 定义一个 Publisher(简单的 Just Publisher)
let publisher1 = Just("Hello, Combine!")
// 定义一个 Subscriber,打印接收到的数据
let subscriber1 = publisher1.sink { value in
print("Just输出:\(value)") // 输出: Hello, Combine!
}
3.2. Future
Future 是 Combine 中的一个发布者,用于在未来某个时刻异步返回一个值(或者失败)。它常用于封装异步操作,例如网络请求、文件读取等。
@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)))
}
}
}
3.3. Deferred
Deferred 是 Combine 中的一个发布者,用于延迟创建和执行发布者。它会在每次订阅时创建一个新的发布者实例,这在需要动态生成发布者,或需要在订阅时才初始化发布者的场景中非常有用。
func fetchData() -> AnyPublisher<String, Never> {
return Deferred {
Future { promise in
print("Start fetching...")
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
promise(.success("Fetched Data"))
}
}
}
.eraseToAnyPublisher()
}
let data = fetchData()
sleep(2)
print("data.sink start")
data.sink { value in
print("Received: \(value)")
}
打印如下:
data.sink start
Start fetching...
Received: Fetched Data
3.4. Empty
Empty 是 Combine 中的一个发布者,专门用于立即完成并不会发布任何值的情况。它常用于不需要发送数据的场景,或者只是为了提供一个结束事件(完成或失败)。
func fetchData(shouldFetch: Bool) -> AnyPublisher<String, Never> {
if shouldFetch {
return Just("Real data").eraseToAnyPublisher()
} else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}
}
let cancellable = fetchData(shouldFetch: false)
.sink(receiveCompletion: { print("Done") },
receiveValue: { print("Value: \($0)") })
// 输出:Done
Empty只会走到receiveCompletion ,不会走receiveValue
3.5. Fail
Fail用于发布错误,主要用于模拟失败场景。
enum MyError: Error {
case somethingWentWrong
}
let failPublisher = Fail<String, MyError>(error: .somethingWentWrong)
let cancellable = failPublisher
.sink(
receiveCompletion: { completion in
print("Completion: \(completion)")
},
receiveValue: { value in
print("Value: \(value)")
}
)
// 输出:Completion: failure(somethingWentWrong)
3.6. Timer.TimerPublisher
生成定时事件流的发布者,常用于需要定时更新的 UI 场景。
可以指定时间间隔来发布数据。
let timerPublisher = Timer.publish(every: 1.0, on: .main, in: .default)
let cancellable = timerPublisher.autoconnect()
.sink { time in print("Current time: \(time)") }
示例 1:SwiftUI 中定时更新时间
import SwiftUI
import Combine
struct TimerView: View {
@State private var currentTime = Date()
var body: some View {
Text("Now: \(currentTime.formatted())")
.onReceive(
Timer.publish(every: 1, on: .main, in: .common).autoconnect()
) { time in
self.currentTime = time
}
}
}
示例 2:结合 .prefix 限制次数
var fetcchCancellables = Set<AnyCancellable>()
let cancellable0 = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.prefix(5)
.sink { time in
print("Timer: \(time)")
}.store(in: &fetcchCancellables)// 输出5次后自动完成
与传统Timer对比:
特性Combine Timer.TimerPublisher传统 Timer
组合能力✅ 易与其他流合并(例如 .combineLatest, .debounce)❌ 不具备
取消控制✅ .cancel() 即可清除订阅⛔️ 需要显式调用 .invalidate()
生命周期绑定✅ 可与 SwiftUI 的 .onReceive 等绑定❌ 手动管理
Thread 安全✅ 默认在指定线程发出事件❌ 需自行管理线程
可测试性✅ 可 mock/pause 流进行测试❌ 不易测试
3.7. NotificationCenter.Publisher
从系统或自定义通知发布数据。
可以监听系统通知或应用内自定义通知。
3.7.1. 传统写法与 Combine 写法区别:
传统写法
NotificationCenter.default.addObserver(self,
selector: #selector(handleNotification(_:)),
name: .someNotification,
object: nil)
@objc func handleNotification(_ notification: Notification) {
print("Received:", notification)
}
Combine 写法
import Combine
let cancellable = NotificationCenter.default
.publisher(for: .someNotification)
.sink { notification in
print("Received:", notification)
}
//Combine的通知防抖处理
NotificationCenter.default
.publisher(for: .keyboardWillShowNotification)
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.sink { notification in
// 延迟处理,防止多次快速通知
}
//Combine只接受一次通知
let cancellable = NotificationCenter.default
.publisher(for: .init("OneTimeNotification"))
.first() // 👈 只接收第一条通知,然后自动完成
.sink { notification in
print("收到一次通知: \(notification)")
}
3.7.2. 与传统NotificationCenter对比:
特性Combine NotificationCenter.Publisher传统 NotificationCenter
取消监听使用 .cancel() 或 AnyCancellable 自动管理必须手动调用 removeObserver
引用目标sink 中弱引用即可,易于避免泄漏需要传递 self,小心循环引用
生命周期跟随✅ 跟随 SwiftUI .onReceive、.task 等生命周期自动管理❌ 不跟随 SwiftUI 生命周期
只能响应通知✅✅
数据转换(map/filter)✅❌
多流合并✅ .merge, .combineLatest❌
防抖/节流✅ .debounce, .throttle❌
3.8. Publishers.Sequence
将数组或集合等序列转换为发布者,顺序发布每个元素。
适合在数据管道中逐个发布已有的数组数据。(可以先发后收)
let sequencePublisher = Publishers.Sequence(sequence: [1, 2, 3, 4, 5])
sequencePublisher.sink(receiveCompletion: { print($0) }, receiveValue: { print($1) })
// 输出: 1, 2, 3, 4, 5, finished
Publishers.Sequence 等同于下面这个“糖”
[1, 2, 3].publisher
publisher
将Array、Dictionary、String等转成发布者
//2.简单的数据流操作 对数据流进行转换、过滤、合并等操作
let publisher2 = [1, 2, 3, 4, 5].publisher
let subscriber2 = publisher2
.map { $0 * 2 } // 将数据乘以 2
.filter { $0 > 5 } // 过滤出大于 5 的值
.sink { value in
print(value)
}
配合map使用
[1, 2, 3, 4, 5].publisher
.map { $0 * 2 }
.sink { print("结果: \($0)") }
结合.collect() 一起使用
["a", "b", "c", "d"].publisher
.collect(2) // 每次发两个
.sink { print("批量值: \($0)") }
批量值: ["a", "b"]
批量值: ["c", "d"]
那他到底有什么用?
3.8.1. 形成串行队列
场景:上传多张图片,每张上传后再上传下一张
let images = ["img1.jpg", "img2.jpg", "img3.jpg"]
func upload(imageName: String) -> AnyPublisher<String, Never> {
Just("上传完成: \(imageName)")
.delay(for: .seconds(1), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
let cancellable = images.publisher
.flatMap(maxPublishers: .max(1)) { upload(imageName: $0) } // 串行上传
.sink { result in
print(result)
}
Combine 的 flatMap(maxPublishers: .max(1)) 就能保证串行执行
3.9. PassthroughSubject
用于创建手动控制的数据流,可以动态发布数据。
适用于需要手动触发事件的场景,比如按钮点击。
let passthroughSubject = PassthroughSubject<String, Never>()
passthroughSubject.sink(receiveValue: { print($0) })
passthroughSubject.send("Hello")
passthroughSubject.send("World")
// 输出: Hello, World
3.9.1. 场景一: 将 UIKit 控件事件转换为 Publisher
class TextFieldObserver: NSObject, UITextFieldDelegate {
let textChanged = PassthroughSubject<String, Never>()
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as NSString? {
let updatedText = text.replacingCharacters(in: range, with: string)
textChanged.send(updatedText)
}
return true
}
}
使用:
let observer = TextFieldObserver()
textField.delegate = observer
observer.textChanged
.sink { print("用户正在输入: \($0)") }
.store(in: &cancellables)
3.9.2. 场景二:将回调封装成 Combine 流
假设你有一个第三方库回调:
func loadData(completion: @escaping (String) -> Void)
你可以封装为:
class Loader {
let result = PassthroughSubject<String, Never>()
func startLoading() {
loadData { data in
self.result.send(data)
}
}
}
然后使用:
let loader = Loader()
loader.result
.sink { print("结果来了: \($0)") }
.store(in: &cancellables)
loader.startLoading()
3.10. CurrentValueSubject
与 PassthroughSubject 类似,但保存并发布最新的值。
适用于需要随时读取最新值的场景(类似于变量)。
它既是一个 Publisher,也是一个 可变的值容器。它的实战意义在于 —— 它非常适合存储状态,且 值一变就自动通知所有订阅者
3.10.1. 实战场景二:实现表单数据绑定(替代双向绑定)
class FormViewModel {
let username = CurrentValueSubject<String, Never>("")
let password = CurrentValueSubject<String, Never>("")
var isValid: AnyPublisher<Bool, Never> {
Publishers.CombineLatest(username, password)
.map { !$0.isEmpty && !$1.isEmpty }
.eraseToAnyPublisher()
}
}
使用方式(比如在 SwiftUI 或 UIKit 中):
viewModel.username.send("tom")
viewModel.password.send("1234")
viewModel.isValid
.sink { print("表单是否合法:\($0)") }
.store(in: &cancellables)
3.10.2. 实战场景二:状态管理 / ViewModel 状态持有者
enum LoginState {
case loggedOut
case loggingIn
case loggedIn(user: String)
}
class AuthViewModel {
let loginState = CurrentValueSubject<LoginState, Never>(.loggedOut)
func login(username: String) {
loginState.send(.loggingIn)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.loginState.send(.loggedIn(user: username))
}
}
}
使用:
let viewModel = AuthViewModel()
viewModel.loginState
.sink { state in
print("状态更新:\(state)")
}
.store(in: &cancellables)
viewModel.login(username: "Tom")
对比项@PublishedCurrentValueSubject
是否语法糖✅ 是❌ 否
是否可以直接 .send()❌ 不行✅ 可以
是否可以创建后自由传递❌ 不方便(是属性)✅ 灵活(可以放入数组等)
是否可以初始就订阅✅ 是✅ 是
是否适合非 class 使用❌ 不适合✅ 可以独立使用
4. Combine基础使用之Subscriber(订阅者)
4.1. sink
sink 是 Combine 中最常用的订阅者。它允许我们在闭包中定义如何处理发布者发出的数据以及完成或失败的事件。它的两种常见形式分别是:
1.接受数据与完成事件:同时订阅值和完成事件.
2.只接受数据:仅订阅值更新,适合只需要处理数据而忽略完成状态的场景.
Swift
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)")
}
)
4.2. assign
assign 是一个特殊的订阅者,用于将发布者发出的值直接赋值给对象的属性。通常用于 UI 更新,尤其是在 SwiftUI 或 UIKit 中,将发布的数据自动绑定到 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
这里还没有体会到优势,我直接赋值不行吗?秀语法?更简洁?
4.3. 自定义订阅者 (Subscriber)
Combine 允许我们创建自定义订阅者,以便完全控制数据接收和完成处理的方式。实现自定义订阅者需要遵循 Subscriber 协议。自定义订阅者适合于更复杂的需求,例如控制数据接收频率或缓存数据。
Swift
import Combine
class CustomSubscriber: Subscriber {
typealias Input = String
typealias Failure = Never
func receive(subscription: Subscription) {
print("订阅开始")
subscription.request(.max(1)) // 请求一个数据
}
func receive(_ input: String) -> Subscribers.Demand {
print("接收到数据:\(input)")
return .none // 不请求更多数据
}
func receive(completion: Subscribers.Completion<Never>) {
print("完成")
}
}
let publisher = Just("Hello, Custom Subscriber!")
let customSubscriber = CustomSubscriber()
publisher.subscribe(customSubscriber)
在这个例子中,CustomSubscriber 实现了 Subscriber 协议的三个方法。receive(subscription:) 请求一个数据项,receive(_:) 接收到发布的数据,receive(completion:) 处理完成状态。
5. Combine基础使用之Operator(操作符)
5.1. map
map 用于对每个值进行转换,将一个数据类型映射为另一个数据类型,适合简单的映射操作。这个跟Swift高阶操作符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
}
5.2. filter
filter 用于筛选满足条件的值,只发出符合条件的元素,适合条件筛选的场景。
import Combine
let publisher = [1, 2, 3, 4, 5].publisher
let cancellable = publisher
.filter { $0 % 2 == 0 } // 只发出偶数
.sink { value in
print(value) // 输出:2, 4
}
5.3. removeDuplicates
removeDuplicates 用于移除相邻的重复值。
//12 removeDuplicates 移除相邻的重复值。
let publisher13 = [1, 1, 2, 2, 3, 3, 4, 5,6, 4].publisher
publisher13.removeDuplicates().sink { value in
print("Unique value: \(value)") // Output: 1, 2, 3,4,5,6,4
}
打印如下:
Unique value: 1
Unique value: 2
Unique value: 3
Unique value: 4
Unique value: 5
Unique value: 6
Unique value: 4
5.4. combineLatest
所有参与的 Publisher 都至少发出过一个值时,combineLatest 才会触发,
并且每次触发都会提供所有 Publisher 的最新值
//所有参与的 Publisher 都至少发出过一个值时,combineLatest 才会触发,
并且每次触发都会提供所有 Publisher 的最新值。
let publisher1 = PassthroughSubject<String, Never>()
let publisher2 = PassthroughSubject<String, Never>()
let cancellable = Publishers.CombineLatest(publisher1, publisher2)
.sink { value1, value2 in
print("最新的值: \(value1), \(value2)")
}
publisher1.send("A") // 不会触发
publisher2.send("B") // 触发,输出 "最新的值: A, B"
publisher1.send("C") // 触发,输出 "最新的值: C, B"
publisher2.send("D") // 触发,输出 "最新的值: C, D"
5.5. merge
merge 将多个发布者的输出合并成一个流,只要任意发布者发出值,合并后的发布者就会发出该值,适合多个数据流合并的场景。
import Combine
let publisher1 = [1, 2, 3].publisher
let publisher2 = [4, 5, 6].publisher
let cancellable = publisher1
.merge(with: publisher2)
5.5.1. 场景 1:门铃 + 电话通知都能提醒你有人来
let doorbell = PassthroughSubject<String, Never>()
let phoneCall = PassthroughSubject<String, Never>()
doorbell
.merge(with: phoneCall)
.sink { message in
print("📢 提醒:\(message)")
}
.store(in: &cancellables)
doorbell.send("门铃响了,有人来了") // ✅ 输出
phoneCall.send("电话响了,有人送快递") // ✅ 输出
5.6. debounce
debounce:只在指定时间间隔内的最新值发布。 让事件的触发等待一段时间(通常是某个延迟时间),如果在这个延迟时间内事件再次触发,之前的事件会被取消,重新计时。只有当事件不再频繁触发时,才会执行操作。
5.6.1. 示例 1:输入框搜索
用户输入时,你希望用户停止输入一段时间后再触发网络请求,而不是每个字符输入时都触发。
import Combine
import SwiftUI
class SearchViewModel: ObservableObject {
@Published var searchText: String = ""
@Published var searchResults: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .seconds(0.5), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] query in
self?.performSearch(query: query)
}
.store(in: &cancellables)
}
private func performSearch(query: String) {
// 模拟网络请求
print("执行搜索: \(query)")
// 更新搜索结果
searchResults = ["Result 1", "Result 2", "Result 3"]
// 假设从网络返回的结果
}
}
效果:
当用户停止输入超过 0.5 秒后,才会触发搜索。
每次输入时,debounce 会重置倒计时,只有停止输入后才会触发。
5.7. throttle
只允许某个事件在指定的时间间隔内执行一次,即使事件源在该时间内发出了多个值。
5.7.1. 示例 1:限制按钮点击频率
模拟一个按钮,用户点击按钮时希望操作每秒最多执行一次。
let buttonTapped = PassthroughSubject<Void, Never>()
buttonTapped
.throttle(for: .seconds(1), scheduler: RunLoop.main, latest: true)
.sink {
print("按钮点击操作")
}
.store(in: &cancellables)
// 模拟频繁点击
buttonTapped.send() // ✅ 触发
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
buttonTapped.send() // 不会触发
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.1) {
buttonTapped.send() // ✅ 触发
}
效果:
第一次点击会触发操作。
在接下来的 1 秒内的点击不会触发。
超过 1 秒后,再次点击会触发操作。
5.7.2. 示例 2:滚动监听防抖
在某些情况下,例如滚动事件频繁触发时,你可能想限制频率,只在一定时间间隔内执行一次更新。
let scrollViewScrolled = PassthroughSubject<Void, Never>()
scrollViewScrolled
.throttle(for: .seconds(0.5), scheduler: RunLoop.main, latest: false)
.sink {
print("滚动更新操作")
}
.store(in: &cancellables)
// 模拟频繁滚动
scrollViewScrolled.send() // ✅ 触发
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
scrollViewScrolled.send() // 不会触发
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
scrollViewScrolled.send() // ✅ 触发
}
throttle vs debounce 对比
特性debouncethrottle
触发时机事件停止后,等待一定时间每个固定时间间隔触发一次
是否等待事件停止✅ 是❌ 否
是否发出最新事件✅ 是(latest 为 true)✅ 是(latest 为 true)
适用场景处理不频繁触发的事件,如搜索框限制频繁触发的事件,如滚动、按钮点击
5.8. catch
catch 捕获并处理错误,允许在遇到错误时提供备用的发布者进行替代操作,适合处理失败后的回退逻辑。
import Combine
let publisher = Fail<String, Error>(error: URLError(.badURL))
let cancellable = publisher
.catch { _ in Just("备用值") } // 捕获错误并提供一个替代值
.sink { value in
print(value) // 输出:备用值
}
5.9. zip
多个 Publisher 顺序合并,直到所有 Publisher 都发布完成
let publisher10 = [1, 2, 3].publisher
let publisher11 = ["A", "B", "C"].publisher
let zipPublisher = publisher10.zip(publisher11)
zipPublisher.sink { (value1, value2) in
print("zip1: \(value1), \(value2)")
}
打印如下
zip1: 1, A
zip1: 2, B
zip1: 3, C
let publisher10 = [1, 2, 3].publisher
let publisher11 = ["A", "B", "C"].publisher
let combinedPublisher = publisher10.combineLatest(publisher11)
combinedPublisher.sink { (value1, value2) in
print("Combined1: \(value1), \(value2)")
}
上面这个打印什么?�
let publisher10 = [1, 2, 3].publisher
.handleEvents(receiveOutput: { print("publisher10 emits: \($0)") })
let publisher11 = ["A", "B", "C"].publisher
.handleEvents(receiveOutput: { print("publisher11 emits: \($0)") })
let combinedPublisher = publisher10.combineLatest(publisher11)
combinedPublisher.sink { (value1, value2) in
print("Combined1: \(value1), \(value2)")
}
上面这个打印什么?�
5.10. prefix 和 suffix
prefix:获取前 N 个元素。 suffix:获取后 N 个元素。
let publisher12 = [1, 2, 3, 4, 5].publisher
publisher12.prefix(3).sink { value in
print("Prefix value: \(value)") // Output: 1, 2, 3
}
6. Combine基础使用之Cancelable协议
Cancellable 协议是 Combine 框架中非常重要的一个协议,用来管理和控制 Publisher 和 Subscriber 之间的订阅关系。
在 Combine 中,发布者(Publisher)和订阅者(Subscriber)是通过 Cancellable 来建立联系的。Cancellable 协议的作用是:负责取消订阅,以避免内存泄漏或不必要的操作。
Cancellable 只有一个cancel方法
protocol Cancellable {
func cancel()
}
cancel():用于取消订阅,解除 Publisher 与 Subscriber 之间的绑定。
6.1.1. 示例1:取消单个订阅的方法
//取消单个订阅的方法
import Combine
let publisher = ["Hello", "Combine", "Framework"].publisher
// 订阅并获取 Cancellable 实例
let subscription: Cancellable = publisher.sink { value in
print("Received value: \(value)")
}
// 取消订阅
subscription.cancel()
6.1.2. 示例 2:使用 Set<AnyCancellable> 管理多个订阅
import Combine
class ViewModel {
private var cancellables = Set<AnyCancellable>()
func startListening() {
let publisher1 = Just("Hello")
let publisher2 = Just("Combine")
publisher1
.sink { value in
print("Publisher 1: \(value)")
}
.store(in: &cancellables)
publisher2
.sink { value in
print("Publisher 2: \(value)")
}
.store(in: &cancellables)
}
func cancelAllSubscriptions() {
// 取消所有订阅
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
print("Cancelled all subscriptions")
}
}
6.1.3. 示例 3:使用AnyCancellable自动取消
AnyCancellable 是一个特定的 Cancellable 类型,可以在超出范围时自动取消订阅。因此,我们不必手动调用 cancel(),它会在生命周期结束时自动执行。
Swift
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
}
7. 监听某个类的属性变化
class ViewModel: ObservableObject {
@Published var text: String = "Hello, Combine!"
}
class ViewController: UIViewController {
private let viewModel = ViewModel()
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.$text.receive(on: DispatchQueue.main)
.sink { newText in
print("newText===\(newText)")
}
.store(in: &cancellables)
}
@objc func btnClick() {
self.viewModel.text = "测试数据更新"
}
}
打印:
newText===Hello, Combine!
newText===测试数据更新
上面代码中能成功监听到viewModel的属性变化。
传统的KVO:
class ViewModel: NSObject {
@objc dynamic var text: String = ""
}
class ObserverClass {
var viewModel = ViewModel()
init() {
super.init()
viewModel.addObserver(self, forKeyPath: #keyPath(ViewModel.text), options: [.new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(ViewModel.text) {
if let newText = change?[.newKey] as? String {
print("Observed text: \(newText)")
}
}
}
deinit {
viewModel.removeObserver(self, forKeyPath: #keyPath(ViewModel.text))
}
}
特性Combine (现代方式)KVO (传统方式)
内存管理自动管理,使用 .store(in:) 管理生命周期必须手动移除观察者,容易导致内存泄漏
类型安全完全类型安全,编译时即可捕获错误键路径基于字符串,没有类型检查,易出错
组合能力强大的组合能力,支持 combineLatest、map、filter 等操作符无法与其他事件流结合
错误处理具有灵活的错误处理机制,如 catch 和 tryCatch错误处理困难,调试不直观
线程与调度支持 receive(on:),轻松处理线程切换必须显式处理线程调度,容易出错
维护与可读性代码清晰,响应式编程模式减少了冗余逻辑代码容易复杂,特别是需要多个观察者时











网友评论