让我们从 技术原理、核心功能、实际案例 和 体系结构 四个维度,深入探讨 SwiftUI 中 onReceive
的完整使用体系。
一、技术原理:Combine 与 SwiftUI 的桥梁 #
1. Publisher 与 Subscriber 模型 #
- Publisher:数据源的抽象,可以发送值(
Output
)或错误(Failure
)。 - Subscriber:接收并处理来自 Publisher 的数据。
- Operator:在 Publisher 和 Subscriber 之间转换或处理数据流。
onReceive
是 SwiftUI 提供的一个 Subscriber 封装,专门用于在视图中订阅 Publisher,并将接收到的值转换为视图的副作用(如更新状态)。
// 原理简化版实现
func onReceive<P>(
_ publisher: P,
perform action: @escaping (P.Output) -> Void
) -> some View where P : Publisher, P.Failure == Never
二、核心功能:支持的 Publisher 类型 #
1. 基础类型 #
(1) Just
: 立即发送一个值并结束
#
.onReceive(Just("Hello")) { value in
print(value) // 立即输出 "Hello"
}
(2) Future
: 异步操作完成后发送单个值
#
let futurePublisher = Future<String, Never> { promise in
DispatchQueue.global().async {
promise(.success("Data loaded"))
}
}
.onReceive(futurePublisher) { result in
print(result) // 异步完成后触发
}
(3) PassthroughSubject
: 手动控制的信号发射器
#
let subject = PassthroughSubject<String, Never>()
.onReceive(subject) { value in
print("Received: \(value)")
}
// 外部触发
subject.send("Message")
2. 系统集成 #
(1) NotificationCenter
#
// 监听键盘高度变化
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { notification in
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
print("Keyboard height: \(keyboardFrame.height)")
}
(2) Timer
#
// 精确控制定时器生命周期
let timerPublisher = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
.onReceive(timerPublisher) { _ in
self.counter += 1
}
.onDisappear {
timerPublisher.upstream.connect().cancel() // 手动取消
}
(3) URLSession
网络请求
#
struct NetworkDemo: View {
@State private var data: String = ""
var body: some View {
Text(data)
.onAppear {
let url = URL(string: "https://api.example.com/data")!
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: String.self, decoder: JSONDecoder())
.replaceError(with: "Error")
.receive(on: DispatchQueue.main)
.sink { self.data = $0 }
.store(in: &cancellables) // 需要持有 Cancellable
}
}
}
3. 高级组合 #
(1) 合并多个 Publisher #
let publisher1 = PassthroughSubject<String, Never>()
let publisher2 = PassthroughSubject<Int, Never>()
.onReceive(publisher1.combineLatest(publisher2)) { (text, number) in
print("Combined: \(text), \(number)")
}
(2) 使用 debounce
防抖
#
// 用于搜索框输入防抖
let searchTextPublisher = PassthroughSubject<String, Never>()
.onReceive(
searchTextPublisher
.debounce(for: .seconds(0.5), scheduler: RunLoop.main)
) { query in
performSearch(query: query)
}
三、体系结构:与 MVVM 的深度整合 #
1. ViewModel 中的 Publisher #
class UserViewModel: ObservableObject {
// 公开只读 Publisher
private let loginStatusSubject = PassthroughSubject<Bool, Never>()
var loginStatusPublisher: AnyPublisher<Bool, Never> {
loginStatusSubject.eraseToAnyPublisher()
}
func login() {
// 模拟网络请求
DispatchQueue.global().async {
self.loginStatusSubject.send(true)
}
}
}
2. View 层的响应 #
struct LoginView: View {
@StateObject var viewModel = UserViewModel()
@State private var isLoggedIn = false
var body: some View {
VStack {
Button("Login") { viewModel.login() }
if isLoggedIn {
Text("Welcome!")
}
}
.onReceive(viewModel.loginStatusPublisher) { success in
isLoggedIn = success
}
}
}
四、实战案例大全 #
案例 1:实时表单验证 #
struct SignupForm: View {
@State private var username = ""
@State private var isValid = false
var body: some View {
VStack {
TextField("Username", text: $username)
Text(isValid ? "Valid" : "Invalid")
}
.onReceive(
$username
.debounce(for: 0.5, scheduler: RunLoop.main)
.map { $0.count >= 5 }
) { isValid = $0 }
}
}
案例 2:跨视图通信 #
// 全局事件总线
class EventBus {
static let shared = EventBus()
let refreshEvent = PassthroughSubject<Void, Never>()
}
struct ParentView: View {
var body: some View {
VStack {
ChildView()
OtherView()
}
}
}
struct ChildView: View {
var body: some View {
Button("Refresh All") {
EventBus.shared.refreshEvent.send()
}
}
}
struct OtherView: View {
@State private var counter = 0
var body: some View {
Text("Count: \(counter)")
.onReceive(EventBus.shared.refreshEvent) {
counter += 1
}
}
}
五、关键注意事项 #
1. 生命周期管理 #
- 自动取消:当视图被销毁时,
onReceive
会自动取消订阅 - 手动控制:对于长期存在的 Publisher(如全局通知),需使用
onDisappear
主动取消
.onReceive(globalPublisher) { ... }
.onDisappear {
globalSubscription?.cancel()
}
2. 线程安全 #
- 强制主线程更新:
.onReceive(publisher.receive(on: DispatchQueue.main)) { ... }
3. 错误处理 #
- 必须处理错误:
.onReceive(
networkPublisher
.catch { error in
Just(NetworkError.message(error.localizedDescription))
}
) { ... }
六、性能优化技巧 #
1. 避免重复订阅 #
// 错误:每次渲染都会新建 Publisher
.onReceive(Timer.publish(every: 1).autoconnect()) { ... }
// 正确:在 ViewModel 或全局管理
class TimerManager {
static let shared = Timer.publish(every: 1).autoconnect()
}
.onReceive(TimerManager.shared) { ... }
2. 使用 objectWillChange
自动刷新
#
class DataModel: ObservableObject {
@Published var items: [String] = []
}
struct ListView: View {
@ObservedObject var model: DataModel
var body: some View {
List(model.items, id: \.self) { ... }
.onReceive(model.objectWillChange) {
// 当 items 变化时自动触发
}
}
}
通过这个完整体系,你可以将 onReceive
灵活运用于:
- 数据驱动 UI:实时响应后端推送、数据库变更
- 系统集成:深度结合 CoreLocation、CoreBluetooth 等框架
- 状态协调:复杂视图间的联动操作
- 性能敏感场景:高频数据流的节流控制
建议结合 Combine 的 操作符文档 进一步探索高级用法!