Swift Async/Await Playground正常 项目.onAppear/.task不触发
问题现象
同一段基于Swift Async/Await实现的NotificationCenter监听代码,在Xcode Playground环境中可正常触发回调,移入实际项目的.onAppear/.task生命周期逻辑中时,异步监听逻辑无法正常接收到通知。
问题复现代码:
let n = Notification.Name("event") Task { for await value in NotificationCenter.default.notifications(named: n, object: nil) { if let str = value.object as? String { await asyncPrint(value: str) } } } NotificationCenter.default.post(name: n, object: "event1") NotificationCenter.default.post(name: n, object: "event2") NotificationCenter.default.post(name: n, object: "event3")
根因分析
- 核心是时序不匹配:
NotificationCenter.default.notifications(named:object:)返回的异步序列,需要等Task进入首次异步挂起状态后才会真正完成监听注册。Playground的执行调度逻辑会给异步任务留出启动时间,注册完成后才会执行后续发通知的同步代码;但在实际App的生命周期回调中,.onAppear/.task的闭包是在主线程同步执行的,代码从上到下运行时,刚创建完Task还没等异步监听挂载完成,就已经把三个通知全部发完了,此时监听还没生效,自然收不到任何回调。 - 额外隐患:如果在
.onAppear中手动创建Task,该Task不会和视图生命周期绑定,视图销毁时不会自动取消,多次进入页面触发.onAppear会生成多个重复的监听Task,进一步导致逻辑异常。
修复方案
- 方案1:调整通知发送时序,确保监听完成注册后再发通知
如果必须在当前页面生命周期内发送通知,将发送逻辑延后到下一个主线程RunLoop周期执行,给监听注册留出调度时间:let n = Notification.Name("event") Task { for await value in NotificationCenter.default.notifications(named: n, object: nil) { if let str = value.object as? String { await asyncPrint(value: str) } } } DispatchQueue.main.async { NotificationCenter.default.post(name: n, object: "event1") NotificationCenter.default.post(name: n, object: "event2") NotificationCenter.default.post(name: n, object: "event3") } - 方案2:优先使用SwiftUI原生
.task修饰符承载异步逻辑
不要在.onAppear中手动创建Task,直接将监听逻辑放入.task闭包中。.task会在视图挂载时自动启动异步任务,视图销毁时自动取消任务,避免内存泄漏和重复监听问题:struct ContentView: View { private let customEvent = Notification.Name("event") var body: some View { Text("页面内容") .task { for await value in NotificationCenter.default.notifications(named: customEvent, object: nil) { guard let str = value.object as? String else { continue } await asyncPrint(value: str) } } } } - 方案3:如果需要在监听启动后立刻触发测试通知,直接将通知发送逻辑放在异步序列初始化之后、遍历开始之前即可,此时监听已经完成注册,可以正常收到通知:
.task { let n = Notification.Name("event") let notificationStream = NotificationCenter.default.notifications(named: n, object: nil) // 此处发送的通知可被正常接收 NotificationCenter.default.post(name: n, object: "event1") NotificationCenter.default.post(name: n, object: "event2") NotificationCenter.default.post(name: n, object: "event3") for await value in notificationStream { guard let str = value.object as? String else { continue } await asyncPrint(value: str) } }
补充说明
NotificationCenter的异步序列不会继承通知发送时的线程上下文,如果通知是在子线程发送的,回调中执行UI相关操作必须手动切换回主线程。- 基于Async/Await的通知监听不需要手动移除,Task被取消时监听会自动终止。
内容的提问来源于stack exchange,提问作者LiangWang
相关产品推荐
相关产品推荐

