目录
介绍
一、是什么
Combine 是 Apple 在 iOS 13 / macOS 10.15 引入的一个 响应式编程框架。
提供了一个统一的的数据流处理模型, Publisher → Operator → Subscriber。
二、解决了什么问题
KVO / NotificationCenter / Delegate
各种机制分散在不同 API,缺乏统一抽象,使用combine框架就可以不使用上述方案了。
UI 与数据绑定
数据变化时自动更新 UI
回调地狱(callback hell)
异步操作嵌套多层回调,层层嵌套,难以维护。
比如一个请求依赖另一个请求时,会有两层嵌套。
异步任务的组合
比如要同时发起两个网络请求,等两个都完成后再处理。
三、核心原理是什么
xy: combine 是一个 发布-订阅 模式的 流模型,数据从publisher 流向 operator 最后到达 subscriber,
publisher作为数据源,
operator对数据进行过滤或转换,
subscriber对数据进行消费
Combine 的核心思想是 响应式流模型(Reactive Streams),用“发布-订阅”模式(Publisher-Subscriber Pattern)。
原理关键点:
Publisher (发布者)
定义了可以异步发出的一系列值(包括正常值、完成事件、错误)。
类似一个“数据源”。
Operator (操作符)
连接 Publisher 和 Subscriber。
通过链式调用,组合/转换数据流。
Subscriber (订阅者)
订阅后才会触发数据流。
负责接收 Publisher 发出的值和完成/错误事件。
背压机制 (Backpressure)
Subscriber 可以请求数据的数量,避免 Publisher 过快产生数据导致资源耗尽。
基于 Swift 泛型 + 协议
Publisher 和 Subscriber 都是协议。
Publisher 有 Output 和 Failure 两个关联类型,用于强类型约束。
Publisher
一、@Published
class ViewModel {
@Published
var text = "this is text"
}
// @Published 修饰的String的订阅,只要text的值发生变化就会触发订阅
vm.$text.sink { complete in
switch complete {
case .finished :
print("$text finished")
case .failure(let error):
print("$text failure:\(error)")
}
} receiveValue: { value in
print(value)
}.store(in: &cancellables)
二、Subject
1、PassthroughSubject
class ViewModel {
let passthroughSubjectPub = PassthroughSubject<String, Never>()
}
passthroughSubject订阅时不会触发回调
// passthroughSubject 的订阅
vm.passthroughSubjectPub.sink { complete in
switch complete {
case .finished :
print("passthroughSubject finished")
case .failure(let error):
print("passthroughSubject failure:\(error)")
}
} receiveValue: { value in
print(value)
}.store(in: &cancellables)
PassthroughSubject:只转发手动发送的值,不保存历史。
vm.passthroughSubjectPub.send("passthrough")
2、CurrentValueSubject
CurrentValueSubject:和 PassthroughSubject 类似,但会保留最近一个值
class ViewModel {
let currentValueSubjectPub = CurrentValueSubject<String, Never>("currentValue")
}
CurrentValueSubject订阅时会触发回调,返回当前值
// currentValueSubject 的订阅
vm.currentValueSubjectPub.sink { complete in
switch complete {
case .finished :
print("currentValueSubject finished")
case .failure(let error):
print("currentValueSubject failure:\(error)")
}
} receiveValue: { value in
print(value)
}.store(in: &cancellables)
vm.currentValueSubjectPub.send("currentValue")
三、订阅后立即发出
class ViewModel {
// 下面这些只要订阅就会立即收到结果
let justPub = Just("just")
let emptyPub = Empty<Int,Never>()
let failPub = Fail<Int, MyError>(error: .somethingWrong)
let sequencePub = [1, 2, 3].publisher
}
Just:立即发出一个值并完成。只要订阅就会立即收到Just发出的值
vm.justPub.sink { complete in
switch complete {
case .finished :YI
print("justPub finished")
case .failure(let error):
print("justPub failure:\(error)")
}
} receiveValue: { value in
print(value)
}.store(in: &cancellables)
Empty:不发任何值,直接完成。只要订阅就会立即完成
vm.emptyPub.sink { complete in
switch complete {
case .finished :
print("emptyPub finished")
case .failure(let error):
print("emptyPub failure:\(error)")
}
} receiveValue: { value in
print(value)
}.store(in: &cancellables)
Operator
一、filter
用于过滤数据
[10,20,30,40,50].publisher
.filter({ value in
// 大于20的数据返回,小于等于20的数据被过滤掉
return value > 20
})
.sink { complete in
switch complete {
case .finished:
print("finished")
case .failure(let error):
print("fail \(error)")
}
} receiveValue: { value in
print(value)
}.store(in: &cancellables)
二、map
用于映射数据,或数据类型转换
static func fetchHomeLoginLazyPublisher(param: [String : Any]) -> AnyPublisher<HomeLogin?,NetworkError> {
return Request().fetchDataLazyPublisher(
urlStr: "https://mock.apipost.net/mock/51814f4a3871000/home/login",
httpMethod: "POST",
parameters: param
).map({(responseData:ResponseData<HomeLogin>) in
responseData.data
}).eraseToAnyPublisher()
}
三、flatMap
链式请求
HomeRequest.fetchHomeOnceLazyPublisher()
.map{ homeOnce in
print("homeOnce is \(homeOnce, default: "")")
return homeOnce?.once
}
.flatMap { once in
let param: [String : Any] = [
"once":once ?? "",
"username":"jianghuhike",
"password":"123456"
]
return HomeRequest.fetchHomeLoginLazyPublisher(param: param)
}
.sink { complete in
switch complete {
case .failure(let error):
print("捕获的错误:\(error)")
case .finished:
print("finish")
}
} receiveValue: { homeDes in
print(homeDes ?? "data is nil")
}
.store(in: &cancellables)
并发控制
let userIds = [1, 2, 3, 4, 5]
userIds.publisher
.flatMap(maxPublishers: .max(2)) { id in
print("user id is \(id)")
return Just("user document \(id)") // 并行请求每个用户的文章
}
.collect() // 等所有完成后收集为 [Post]
.sink(receiveCompletion: { print($0) },
receiveValue: { allPosts in
print("总共获取到 \(allPosts) ")
})
.store(in: &cancellables)
四、节流
Timer.publish(every: 1.0, on: .main, in: .default)
.autoconnect()
.print("打印日期:\(Date().description)")
.throttle(for: 2.0, scheduler: RunLoop.main, latest: true)
.sink(
receiveCompletion: { print ("Completion: \($0).") },
receiveValue: { print("Received Timestamp \($0).",terminator: "\n---------------------\n") }
)
.store(in: &cancellables)
节流:滚动位置上报
scrollSubject
.map { $0.y }
.removeDuplicates()
.throttle(for: .seconds(1), scheduler: RunLoop.main, latest: true)
.sink { y in
print("📡 (subject) 滚动位置上报: \(y)")
}
.store(in: &cancellables)
五、防抖
防抖:搜索框输入时,延迟0.5秒后才发起请求
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: textField)
.compactMap { ($0.object as? UITextField)?.text }
.debounce(for: .milliseconds(500), scheduler: RunLoop.main) // 防抖:0.5 秒
.removeDuplicates() // 去重:避免相同输入重复触发
.sink { [weak self] text in
print("🔍 开始搜索: \(text)")
self?.search(keyword: text)
}
.store(in: &cancellables)
Subscriber
一、sink
只接收完成后的值
[1, 2, 3].publisher
.sink { value in
print("value is \(value)")
}
.store(in: &cancellables)
接收完成事件和完成后的值
[1,2,3].publisher
.sink { complete in
switch complete {
case .finished:
print("finieshe")
case .failure(let error):
print("error is \(error)")
}
} receiveValue: { value in
print("value is \(value)")
}
.store(in: &cancellables)
二、assign
数据模型绑定
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: usernameField)
.map { ($0.object as? UITextField)?.text ?? "" }
.assign(to: \.userName, on: self)
.store(in: &cancellables)
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: passwordField)
.map { ($0.object as? UITextField)?.text ?? "" }
.assign(to: \.password, on: self)
.store(in: &cancellables)
行者常至,为者常成!