NotificationCenter — 本质、工作原理与通知架构

作者: IT Sectr 发布日期: 2026-03-18 阅读时间: 10 分钟

NotificationCenter — 是 iOS 的系统机制,用于在应用程序组件之间发送和接收通知,无需发送者和接收者之间直接连接。基于 Observer 模式,NotificationCenter 允许对象订阅事件并异步响应。根据 Apple Documentation (2025),NSNotificationCenter 既支持通过 post(name:object:) 同步发送通知,也支持通过 NotificationQueue 延迟发送。通知中心在单个进程内运行,不跨越应用程序边界。

要点

  • NotificationCenter — Observer 模式在 iOS 组件之间进行事件交换的实现。
  • addObserver 将对象订阅到具有特定名称和发送者对象的通知。
  • post(name:object:) 同步向所有已订阅的观察者发送通知。
  • removeObserver 必须在 deinit 中调用,否则发送通知时会崩溃。
  • NotificationQueue 允许延迟通知以实现异步传递。

什么是 NotificationCenter?

NotificationCenter (NSNotificationCenter) — 是 iOS 内置的用于实现对象之间松散耦合通信的机制。Observer 模式允许一个对象(发送者)在无需直接引用的情况下通知多个其他对象(观察者)事件的发生。NotificationCenter 使用三个实体:Notification.Name(通知标识符)、Notification(数据容器)和 NotificationCenter(调度器)。每个应用程序都有一个共享的 default center。

NSNotification 和 Notification.Name

Notification.Name — 是标识通知类型的结构体。通过 extension Name: Notification.Name("MyNotification") 创建。Notification — 是包含 name、object(发送者)和 userInfo(数据字典)的对象。系统通知声明为常量:UIApplication.didBecomeActiveNotification、UIResponder.keyboardWillShowNotification。自定义通知应通过 extension 分组以避免名称冲突。名称应为反向域名。

swift
// 定义自定义通知
extension Notification.Name {
    static let dataDidUpdate =
        Notification.Name("com.app.dataDidUpdate")
    static let userLoggedOut =
        Notification.Name("com.app.userLoggedOut")
}

// 发送带数据的通知
let userInfo: [String: Any] = [
    "userId": 123,
    "timestamp": Date()
]
NotificationCenter.default.post(
    name: .dataDidUpdate,
    object: nil,
    userInfo: userInfo
)

添加观察者 (addObserver)

观察者通过 addObserver(_:selector:name:object:) 方法订阅通知。Selector — 收到通知时将被调用的方法。object 参数允许过滤来自特定发送者的通知。如果 object 为 nil,观察者将收到来自任何发送者的具有指定名称的所有通知。从 iOS 9 开始,addObserver 对于 block-based API 不需要手动移除,但 selector-based 仍然需要 removeObserver。

swift
// 订阅通知 (selector-based)
NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleDataUpdate),
    name: .dataDidUpdate,
    object: nil
)

@objc func handleDataUpdate(_ notification: Notification) {
    guard let userId = notification.userInfo?["userId"] as? Int else { return }
    updateUI(for: userId)
}

// 订阅通知 (block-based, iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
    forName: .dataDidUpdate,
    object: nil,
    queue: .main
) { [weak self] notification in
    guard let self else { return }
    self.handleNotification(notification)
}

NotificationCenter 如何工作?

NotificationCenter 存储一个映射表(name → 观察者集合)。当发送者调用 post(name:object:) 时,通知中心同步遍历所有订阅此名称的观察者,并调用它们的 selector 或 block。关键特性:post 会阻塞当前线程直到所有处理程序完成。如果处理程序执行繁重操作,这将延迟发送者。NotificationQueue 通过延迟通知传递来解决此问题。

同步发送 (post)

post(name:object:userInfo:) 方法立即向所有观察者发送通知。调用是同步的 — post 之后的代码仅在所有处理程序完成后执行。观察者的调用顺序不保证,并且可能在运行之间变化。对于顺序处理,请使用带有 coalescing 的 NotificationQueue。不要在同一通知的处理程序内部调用 post — 这会导致无限递归。

延迟发送 (NotificationQueue)

NotificationQueue 将通知添加到队列以进行异步传递。支持 coalescing(合并相同通知)和选择传递队列(asap、idle、modal)。Coalescing 对于频繁事件(加载进度)非常有用,当只需要通知最后一个值时。NotificationQueue 使用 run loop 来触发,因此仅在有活动 run loop 的线程中工作。

swift
// 通过 NotificationQueue 延迟发送
let notification = Notification(
    name: .dataDidUpdate,
    object: self,
    userInfo: ["progress": 0.5]
)

// 合并:多条通知合并为一条
NotificationQueue.default.enqueue(
    notification,
    postingStyle: .whenIdle,
    coalesceMask: .onName,
    forModes: [.common]
)

// 通过 DispatchQueue 异步发送
DispatchQueue.main.async {
    NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}

Notification vs Delegate vs KVO

iOS 提供了三种主要的对象间通信机制:NotificationCenterDelegateKVO(Key-Value Observing)。每种都解决了通知问题,但在耦合性、性能和类型安全方面有不同的权衡。机制的选择取决于“一对一”或“一对多”关系以及数据传输的需求。

特性NotificationCenterDelegateKVO
耦合性松散(通知名称)强(协议)中等(键)
关系一对多一对一一对多
类型安全低(userInfo 作为 Dictionary)高(协议方法)中等(Any?)
性能中等(遍历表)高(直接调用)低(NSObject)
异步性同步(post 阻塞)在发送者线程中同步变更时同步

何时选择 NotificationCenter

NotificationCenter 非常适合多个独立组件需要响应的事件。示例:应用程序设置更改、用户注销、在后台接收推送通知。NotificationCenter 也适用于松散耦合的模块(功能 A 不需要知道功能 B)。缺点 — 没有类型安全:userInfo 键是字符串,而不是枚举。

何时选择 Delegate 或 KVO

Delegate 选择用于具有明确契约的一对一关系(tableView.delegate)。Delegate 更快且类型更安全。KVO 选择用于观察模型的特定属性的变化(isLoading、progress)。KVO 需要继承 NSObject,并且可能在调试时造成困难(魔术键字符串)。在现代 Swift 中,Combine 和 async sequences 替代了所有三种方法。

AddObserver:同步和异步通知

addObserver 方法支持两种订阅变体:selector-based(传统)和 block-based(带闭包)。Selector-based 需要 @objc 兼容性和手动移除观察者。Block-based(iOS 9+)允许使用 capture list,并在使用没有强引用的 block 时由 OS 自动管理。Block-based 还支持 queue — 观察者在指定的队列中接收通知。

Selector-based addObserver

通过 selector 进行订阅的传统方式。处理程序方法必须用 @objc 标记并接受可选的 Notification。优点:任何类都可以使用,包括 legacy Objective-C。缺点:缺少 selector 的类型安全、selector 名称拼写错误的风险、deinit 中必须调用 removeObserver。如果观察者在对象之前被移除,处理程序将不会被调用。

Block-based addObserver

Block-based API 接受一个闭包,在收到通知时执行。queue 参数 确定 block 在哪个队列中执行 — main queue 用于 UI 更新或 background queue 用于数据处理。返回的 NSObjectProtocol 值用于移除观察者:NotificationCenter.default.removeObserver(observer)。在现代 Swift 中优先使用 Block-based。

swift
protocol NotificationToken {
    func dispose()
}

extension NotificationCenter {
    func observe(
        name: NSNotification.Name,
        object: Any? = nil,
        queue: OperationQueue? = .main,
        using block: @escaping (Notification) -> Void
    ) -> NotificationToken {
        let observer = addObserver(forName: name, object: object,
                                   queue: queue, using: block)
        return NotificationTokenWrapper(observer: observer, center: self)
    }
}

// 使用自动移除
class ViewModel {
    private var tokens: [NotificationToken] = []

    func startObserving() {
        let token = NotificationCenter.default.observe(
            name: .dataDidUpdate,
            queue: .main
        ) { [weak self] notification in
            self?.handleUpdate(notification)
        }
        tokens.append(token)
    }

    deinit {
        tokens.forEach { $0.dispose() }
    }
}

内存管理和观察者移除

内存泄漏 — 使用 NotificationCenter 时的主要问题之一。如果观察者在释放前没有被移除,发送通知时中心将尝试调用已释放对象的方法,导致 EXC_BAD_ACCESS。从 iOS 9 开始,block-based addObserver 使用弱引用,但 selector-based 仍然需要手动 removeObserver。最佳实践:在 deinit 中移除观察者。

何时调用 removeObserver

Selector-based:务必在 deinit 中调用 NotificationCenter.default.removeObserver(self)。如果观察者订阅了多个通知,可以同时移除所有(不带参数)或按名称移除特定通知。Block-based:通过 removeObserver 使用从 addObserver 获得的 token 移除。对于 iOS 9+ 上的 block-based,不会发生泄漏,但为了性能仍然建议移除:已释放的观察者不会在 post 时被遍历。

swift
class SafeObserver {
    private var observers: [NSObjectProtocol] = []

    func addSubscriptions() {
        let token1 = NotificationCenter.default.addObserver(
            forName: .dataDidUpdate, object: nil,
            queue: .main) { [weak self] _ in
            self?.refreshData()
        }
        let token2 = NotificationCenter.default.addObserver(
            forName: .userLoggedOut, object: nil,
            queue: .main) { [weak self] _ in
            self?.logout()
        }
        observers.append(contentsOf: [token1, token2])
    }

    deinit {
        observers.forEach { NotificationCenter.default.removeObserver($0) }
    }

    private func refreshData() { }
    private func logout() { }
}

通过 Token 模式的弱引用

Token 模式自动管理观察者。订阅时返回一个 token 对象(NSObjectProtocol),该对象在释放时自动移除观察者。NotificationTokenWrapper 存储对 NotificationCenter 和观察者 token 的弱引用,在 deinit 中调用 removeObserver。这使 NotificationCenter 更接近 Combine 方法,其中 AnyCancellable 管理订阅的生命周期。

多线程环境中的 NotificationCenter

线程安全 NotificationCenter 保证 post 可以从任何线程调用,并且所有观察者将在调用 post 的同一线程中收到通知。这对于多线程应用程序至关重要:如果通知是从后台线程发送的,处理程序也将在后台线程中执行。对于 UI 更新,需要通过 DispatchQueue.main.async 将处理调度到 main queue。

post 和 addObserver 的线程安全性

NotificationCenter 对于来自不同线程的 post 和 addObserver 调用是线程安全的。内部同步 使用锁,因此来自多个线程的频繁 post 可能会造成争用。对于高负载场景(1000 个文件的加载进度),请使用单独的通知队列或 Combine publisher。带有 postingStyle .now 的 NotificationQueue 等同于直接 post。

通过 Combine 进行异步传递

NotificationCenter 通过 NotificationCenter.default.publisher(for:object:) 支持 Combine publisher。Publisher 将每个通知转换为 Combine 事件,可以通过 map、filter、debounce 和 throttle 进行转换。这解决了同步传递的问题:Combine 在指定的 Scheduler 上异步处理通知。NotificationCenter.publisher — 是 legacy 机制和现代响应式编程之间的桥梁。

swift
import Combine

class ReactiveViewModel {
    private var cancellables = Set<AnyCancellable>()

    func setupCombineSubscription() {
        NotificationCenter.default
            .publisher(for: .dataDidUpdate)
            .receive(on: DispatchQueue.main)
            .compactMap { $0.userInfo?["progress"] as? Float }
            .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
            .sink { [weak self] progress in
                self?.progressLabel.text = "\(Int(progress * 100))%"
            }
            .store(in: &cancellables)
    }
}

常见问题

NotificationCenter 是线程安全的吗?

是的,NotificationCenter 对于从任何线程调用 post 和 addObserver 是线程安全的。然而,处理程序在调用 post 的同一线程中执行。对于 UI 更新,请在 block-based addObserver 中使用 queue: .main 或在处理程序内部使用 DispatchQueue.main.async。带有 receive(on:) 的 Combine publisher 也可以解决线程问题。

如果不移除观察者会怎样?

Selector-based:在观察者释放后发送通知时崩溃 EXC_BAD_ACCESS。Block-based(iOS 9+):由于弱引用没有泄漏,但通知中心会继续在内存中保留 block 直到显式调用 removeObserver。建议始终在 deinit 中移除观察者,或使用 Token 模式进行自动管理。

NotificationCenter 和 KVO 有什么区别?

NotificationCenter — 在不相关组件之间广播任意事件。KVO — 观察特定对象的特定属性的变化。KVO 需要继承 NSObject,并在属性通过 setter 更改时自动通知。NotificationCenter 仅在显式调用 post 时通知。对于模型观察,KVO 或 Combine 更受青睐。

一个应用程序中有多少个 NotificationCenter?

每个应用程序进程一个 default center。可以通过 NotificationCenter() 创建额外的中心,但实践中使用共享的 default。每个中心独立工作 — 一个中心中的 post 不会传递到另一个中心的观察者。对于模块隔离,请使用通过反向域名通知名称创建的单独的 Name 命名空间。

Combine 会取代 NotificationCenter 吗?

部分取代。Combine 提供了 NotificationCenter.Publisher,它将 NotificationCenter 包装在响应式流中。Combine 解决了同步问题(通过 receive(on:)),添加了转换操作符和自动订阅管理(AnyCancellable)。然而,NotificationCenter 仍然用于 iOS 系统通知(UIApplication、UIKeyboard)和 legacy 代码。Combine 是一个附加层,而不是替代品。

总结

  • NotificationCenter — Observer 模式在 iOS 中实现松散耦合“一对多”通信的实现。
  • post 同步向当前线程中的所有观察者发送通知,阻塞发送者。
  • addObserver 支持 selector-based(带 @objc)和 block-based(带 capture list 和 queue)订阅。
  • removeObserver 对于 selector-based 订阅必须在 deinit 中调用,否则崩溃。
  • NotificationQueue 为频繁事件提供带有 coalescing 的延迟传递。
  • 线程安全 保证从任何线程工作,但处理程序在发送者的线程中执行。
  • 使用 Token 模式或 Combine publisher 进行安全和现代的订阅管理。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读