NotificationCenter 作为 iOS 开发中的经典组件,为开发者提供了灵活的广播——订阅机制。然而,随着 Swift 并发模型的不断演进,传统基于字符串标识和 userInfo 字典的通知方式暴露出了诸多问题:线程安全隐患、拼写错误风险、类型转换不安全等,这些问题往往只有在运行时才会被发现。
为了彻底解决这些痛点,Swift 6.2 在 Foundation 中引入了全新的并发安全通知协议:NotificationCenter.MainActorMessage 和 NotificationCenter.AsyncMessage。它们充分利用 Swift 的类型系统和并发隔离特性,让消息的发布与订阅在编译期就能得到验证,从根本上杜绝了“线程冲突”和“数据类型错误”等常见问题。
传统 Notification 的局限性
让我们先回顾一下传统 Notification 的使用方式:
// 发布通知
NotificationCenter.default.post(
    name: .userDidLogin,
    object: authManager,
    userInfo: ["userID": user.id]
)
// 订阅通知
NotificationCenter.default.addObserver(
    forName: .userDidLogin,
    object: authManager,
    queue: .main
) { notification in
    guard let id = notification.userInfo?["userID"] as? String else { 
        return 
    }
    print("User logged in:", id)
}虽然使用简单,但这种方式存在明显的安全隐患:
- 字符串标识符易错:拼写错误只能在运行时发现,增加了调试成本
- 类型安全缺失:userInfo需要手动类型转换,容易遗漏字段或转换失败
- 并发行为不明确:回调执行的线程取决于发送方,容易引发线程安全问题
- 编译期无法验证:错误的使用方式只有在运行时才会暴露
特别是在使用 @ModelActor 等新并发特性时,传统通知机制甚至可能直接导致应用崩溃,因为我们无法在订阅时精确控制执行线程。
NotificationCenter.Message:类型安全的解决方案
为了解决这些问题,Swift 社区在 2024 年底提出了 “Concurrency-Safe Notification” 提案,并在 Swift 6.2 中正式推出。
在最终的实现中,NotificationCenter 提供了两个明确的消息协议:
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, *)
extension NotificationCenter {
    public protocol MainActorMessage: SendableMetatype {
        associatedtype Subject
        static var name: Notification.Name { get }
        static func makeMessage(_ notification: Notification) -> Self?
        static func makeNotification(_ message: Self, object: Self.Subject?) -> Notification
    }
    
    public protocol AsyncMessage: Sendable {
        associatedtype Subject
        static var name: Notification.Name { get }
        static func makeMessage(_ notification: Notification) -> Self?
        static func makeNotification(_ message: Self, object: Self.Subject?) -> Notification
    }
}该体系包含两个核心协议:
- MainActorMessage:主线程消息,保证同步执行且绑定到- @MainActor
- AsyncMessage:异步消息,支持- Sendable并可跨线程安全传递
实践指南
声明消息类型
首先,我们需要定义一个符合 MainActorMessage 的消息类型:
public class DownloadManager {
    static let shared = DownloadManager()
}
public struct DownloadDidFinish: MainActorMessage {
    public typealias Subject = DownloadManager // 对应 NotificationCenter.default.post 中的 object 类型
    public static var name: Notification.Name {
        .init("DownloadManager.DownloadDidFinish")
    }
  	// 强类型的消息内容,替代 userInfo 字典
    public let fileURL: URL // 相当于旧版本中 userInfo 的 ["fileURL": URL]
    public let success: Bool // 相当于旧版本中 userInfo 的 ["success": Bool]
}在这个示例中:
- Subject定义了消息发送者的类型
- fileURL和- success是强类型的消息内容
- name为兼容性保留(如果仅使用新 API,可以省略)
定义消息标识符(可选)
为了提供更好的 API 体验,我们可以定义消息标识符:
extension NotificationCenter.MessageIdentifier
where Self == NotificationCenter.BaseMessageIdentifier<DownloadDidFinish> {
    static var downloadDidFinish: Self { .init() }
}这样可以在订阅时使用 .downloadDidFinish 这样的简洁语法。
发送消息
新 API 提供了两种发送方式:
// 关联特定实例
NotificationCenter.default.post(
    DownloadDidFinish(fileURL: url, success: true),
    subject: DownloadManager.shared
)
// 全局广播
NotificationCenter.default.post(
    DownloadDidFinish(fileURL: url, success: false)
)订阅消息
同步回调方式
// 仅响应特定实例的消息
let token = NotificationCenter.default.addObserver(
    of: DownloadManager.shared,
    for: .downloadDidFinish
) { message in
    print("下载完成,成功:\(message.success)")
}
// 响应所有 DownloadDidFinish 消息
let token = NotificationCenter.default.addObserver(
    for: DownloadDidFinish.self
) { message in
    print("文件:\(message.fileURL),成功:\(message.success)")
}异步流式订阅
对于 AsyncMessage,还可以使用 Swift 的 AsyncSequence 特性:
struct DownloadFinishAsyncMessage: NotificationCenter.AsyncMessage {
    typealias Subject = DownloadManager
    public let fileURL: URL
    public let success: Bool
}
// 使用 for await in 语法
Task {
    for await msg in NotificationCenter.default.messages( 
        of: DownloadManager.shared,
        for: DownloadFinishAsyncMessage.self
    ) {
        print("异步收到下载完成:", msg.fileURL)
    }
}与传统 API 的无缝集成
如果您的项目需要逐步迁移,或者要与使用传统 API 的第三方库兼容,可以实现协议约定的转换方法:
extension DownloadDidFinish {
    // 将传统 Notification 转换为 Message
    public static func makeMessage(_ notification: Notification) ->
    DownloadDidFinish? {
        guard
            let info = notification.userInfo,
            let url = info["fileURL"] as? URL,
            let ok = info["success"] as? Bool
        else {
            return nil
        }
        return Self(fileURL: url, success: ok)
    }
		// 将 Message 转换为传统 Notification
    public static func makeNotification(_ message: DownloadDidFinish, object: DownloadManager?) -> Notification {
        Notification(
            name: name,
            object: object,
            userInfo: [
                "fileURL": message.fileURL,
                "success": message.success
            ]
        )
    }
}有了这些转换方法,新旧 API 可以完全互通:
// 使用传统方式发送
NotificationCenter.default.post(
    name: .init(rawValue: "DownloadManager.DownloadDidFinish"),
    object: nil,
    userInfo: [
        "fileURL": URL(string: "https://www.baidu.com")!,
        "success": false,
    ])新 API 的订阅者会收到自动转换的 DownloadDidFinish 消息。
// 使用传统的方式订阅
.onReceive(
    NotificationCenter.default
        .publisher(for: DownloadDidFinish.name))
{ notification in
    guard let userInfo = notification.userInfo,
          let success = userInfo["success"] as? Bool
    else {
        return
    }
    print(success)
}SwiftUI 集成方案
虽然 SwiftUI 暂未提供内置的新消息订阅修饰器,但我们可以轻松自定义:
struct MessageIdentifierObserverModifier<ID>: ViewModifier
    where ID: NotificationCenter.MessageIdentifier,
    ID.MessageType: NotificationCenter.MainActorMessage,
    ID.MessageType.Subject: AnyObject
{
    let messageID: ID
    let subject: ID.MessageType.Subject?
    let perform: (ID.MessageType) -> Void
    @State var token: NotificationCenter.ObservationToken?
    init(
        messageID: ID,
        subject: ID.MessageType.Subject?,
        perform: @escaping (ID.MessageType) -> Void)
    {
        self.messageID = messageID
        self.subject = subject
        self.perform = perform
    }
    func body(content: Content) -> some View {
        content
            .onAppear {
                guard token == nil else { return }
                if let subject {
                    token = NotificationCenter.default.addObserver(
                        of: subject,
                        for: messageID
                    ) { perform($0)
                    }
                } else {
                    token = NotificationCenter.default.addObserver(
                        of: subject,
                        for: ID.MessageType.self)
                    { perform($0)
                    }
                }
            }
            .onDisappear {
                if let t = token {
                    NotificationCenter.default.removeObserver(t)
                }
            }
    }
}
struct MessageTypeObserverModifier<Message>: ViewModifier
    where Message: NotificationCenter.MainActorMessage,
    Message.Subject: AnyObject
{
    let messageType: Message.Type
    let subject: Message.Subject?
    let perform: (Message) -> Void
    @State private var token: NotificationCenter.ObservationToken?
    init(
        messageType: Message.Type,
        subject: Message.Subject? = nil,
        perform: @escaping (Message) -> Void)
    {
        self.messageType = messageType
        self.subject = subject
        self.perform = perform
    }
    func body(content: Content) -> some View {
        content
            .onAppear {
                guard token == nil else { return }
                token = NotificationCenter.default.addObserver(
                    of: subject,
                    for: messageType,
                    using: perform)
            }
            .onDisappear {
                if let t = token {
                    NotificationCenter.default.removeObserver(t)
                    token = nil
                }
            }
    }
}
extension View {
    func onReceive<Message>(
        of messageType: Message.Type,
        subject: Message.Subject? = nil,
        perform: @escaping (Message) -> Void) -> some View
        where Message: NotificationCenter.MainActorMessage,
        Message.Subject: AnyObject
    {
        modifier(
            MessageTypeObserverModifier(
                messageType: messageType,
                subject: subject,
                perform: perform))
    }
}
extension View {
    func onReceive<ID>(
        for messageID: ID,
        subject: ID.MessageType.Subject? = nil,
        perform: @escaping (ID.MessageType) -> Void) -> some View
        where
        ID: NotificationCenter.MessageIdentifier,
        ID.MessageType: NotificationCenter.MainActorMessage,
        ID.MessageType.Subject: AnyObject
    {
        modifier(MessageIdentifierObserverModifier(
            messageID: messageID,
            subject: subject,
            perform: perform))
    }
}现在便可以在 SwiftUI 视图中优雅地订阅 MainActorMessage 消息了:
// 针对特定对象
.onReceive(for: .downloadDidFinish, subject: DownloadManager.shared) { message in
    print(message.success)
}
// 全局响应
.onReceive(of: DownloadDidFinish.self) {
    print(message.success)
}迁移建议
对于现有项目,建议采用渐进式迁移策略:
- 新功能优先:新开发的功能直接使用 NotificationCenter.Message
- 关键路径迁移:对于并发敏感的通知,优先迁移为 MainActorMessage或AsyncMessage
- 保持兼容性:实现 makeMessage和makeNotification方法,确保新旧代码能够互通
- 逐步替换:在合适的时机将传统通知调用替换为新 API
总结
NotificationCenter.Message 为 Swift 的通知系统带来了期待已久的类型安全和并发安全特性。通过编译期检查和明确的隔离语义,它不仅提升了代码的可靠性,还提供了更好的开发体验。
如果您正在使用 Swift 6.2 且项目最低版本为 iOS 26 / macOS 26,不妨在新项目中尝试这套并发安全的通知方式,相信它会让您的代码更加安全和优雅。