NotificationCenter — 是 iOS 的系统机制,用于在应用程序组件之间发送和接收通知,无需发送者和接收者之间直接连接。基于 Observer 模式,NotificationCenter 允许对象订阅事件并异步响应。根据 Apple Documentation (2025),NSNotificationCenter 既支持通过 post(name:object:) 同步发送通知,也支持通过 NotificationQueue 延迟发送。通知中心在单个进程内运行,不跨越应用程序边界。
要点
NotificationCenter (NSNotificationCenter) — 是 iOS 内置的用于实现对象之间松散耦合通信的机制。Observer 模式允许一个对象(发送者)在无需直接引用的情况下通知多个其他对象(观察者)事件的发生。NotificationCenter 使用三个实体:Notification.Name(通知标识符)、Notification(数据容器)和 NotificationCenter(调度器)。每个应用程序都有一个共享的 default center。
Notification.Name — 是标识通知类型的结构体。通过 extension Name: Notification.Name("MyNotification") 创建。Notification — 是包含 name、object(发送者)和 userInfo(数据字典)的对象。系统通知声明为常量:UIApplication.didBecomeActiveNotification、UIResponder.keyboardWillShowNotification。自定义通知应通过 extension 分组以避免名称冲突。名称应为反向域名。
// 定义自定义通知
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(_:selector:name:object:) 方法订阅通知。Selector — 收到通知时将被调用的方法。object 参数允许过滤来自特定发送者的通知。如果 object 为 nil,观察者将收到来自任何发送者的具有指定名称的所有通知。从 iOS 9 开始,addObserver 对于 block-based API 不需要手动移除,但 selector-based 仍然需要 removeObserver。
// 订阅通知 (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 存储一个映射表(name → 观察者集合)。当发送者调用 post(name:object:) 时,通知中心同步遍历所有订阅此名称的观察者,并调用它们的 selector 或 block。关键特性:post 会阻塞当前线程直到所有处理程序完成。如果处理程序执行繁重操作,这将延迟发送者。NotificationQueue 通过延迟通知传递来解决此问题。
post(name:object:userInfo:) 方法立即向所有观察者发送通知。调用是同步的 — post 之后的代码仅在所有处理程序完成后执行。观察者的调用顺序不保证,并且可能在运行之间变化。对于顺序处理,请使用带有 coalescing 的 NotificationQueue。不要在同一通知的处理程序内部调用 post — 这会导致无限递归。
NotificationQueue 将通知添加到队列以进行异步传递。支持 coalescing(合并相同通知)和选择传递队列(asap、idle、modal)。Coalescing 对于频繁事件(加载进度)非常有用,当只需要通知最后一个值时。NotificationQueue 使用 run loop 来触发,因此仅在有活动 run loop 的线程中工作。
// 通过 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)
}
iOS 提供了三种主要的对象间通信机制:NotificationCenter、Delegate 和 KVO(Key-Value Observing)。每种都解决了通知问题,但在耦合性、性能和类型安全方面有不同的权衡。机制的选择取决于“一对一”或“一对多”关系以及数据传输的需求。
| 特性 | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| 耦合性 | 松散(通知名称) | 强(协议) | 中等(键) |
| 关系 | 一对多 | 一对一 | 一对多 |
| 类型安全 | 低(userInfo 作为 Dictionary) | 高(协议方法) | 中等(Any?) |
| 性能 | 中等(遍历表) | 高(直接调用) | 低(NSObject) |
| 异步性 | 同步(post 阻塞) | 在发送者线程中同步 | 变更时同步 |
NotificationCenter 非常适合多个独立组件需要响应的事件。示例:应用程序设置更改、用户注销、在后台接收推送通知。NotificationCenter 也适用于松散耦合的模块(功能 A 不需要知道功能 B)。缺点 — 没有类型安全:userInfo 键是字符串,而不是枚举。
Delegate 选择用于具有明确契约的一对一关系(tableView.delegate)。Delegate 更快且类型更安全。KVO 选择用于观察模型的特定属性的变化(isLoading、progress)。KVO 需要继承 NSObject,并且可能在调试时造成困难(魔术键字符串)。在现代 Swift 中,Combine 和 async sequences 替代了所有三种方法。
addObserver 方法支持两种订阅变体:selector-based(传统)和 block-based(带闭包)。Selector-based 需要 @objc 兼容性和手动移除观察者。Block-based(iOS 9+)允许使用 capture list,并在使用没有强引用的 block 时由 OS 自动管理。Block-based 还支持 queue — 观察者在指定的队列中接收通知。
通过 selector 进行订阅的传统方式。处理程序方法必须用 @objc 标记并接受可选的 Notification。优点:任何类都可以使用,包括 legacy Objective-C。缺点:缺少 selector 的类型安全、selector 名称拼写错误的风险、deinit 中必须调用 removeObserver。如果观察者在对象之前被移除,处理程序将不会被调用。
Block-based API 接受一个闭包,在收到通知时执行。queue 参数 确定 block 在哪个队列中执行 — main queue 用于 UI 更新或 background queue 用于数据处理。返回的 NSObjectProtocol 值用于移除观察者:NotificationCenter.default.removeObserver(observer)。在现代 Swift 中优先使用 Block-based。
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 中移除观察者。
Selector-based:务必在 deinit 中调用 NotificationCenter.default.removeObserver(self)。如果观察者订阅了多个通知,可以同时移除所有(不带参数)或按名称移除特定通知。Block-based:通过 removeObserver 使用从 addObserver 获得的 token 移除。对于 iOS 9+ 上的 block-based,不会发生泄漏,但为了性能仍然建议移除:已释放的观察者不会在 post 时被遍历。
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 对象(NSObjectProtocol),该对象在释放时自动移除观察者。NotificationTokenWrapper 存储对 NotificationCenter 和观察者 token 的弱引用,在 deinit 中调用 removeObserver。这使 NotificationCenter 更接近 Combine 方法,其中 AnyCancellable 管理订阅的生命周期。
线程安全 NotificationCenter 保证 post 可以从任何线程调用,并且所有观察者将在调用 post 的同一线程中收到通知。这对于多线程应用程序至关重要:如果通知是从后台线程发送的,处理程序也将在后台线程中执行。对于 UI 更新,需要通过 DispatchQueue.main.async 将处理调度到 main queue。
NotificationCenter 对于来自不同线程的 post 和 addObserver 调用是线程安全的。内部同步 使用锁,因此来自多个线程的频繁 post 可能会造成争用。对于高负载场景(1000 个文件的加载进度),请使用单独的通知队列或 Combine publisher。带有 postingStyle .now 的 NotificationQueue 等同于直接 post。
NotificationCenter 通过 NotificationCenter.default.publisher(for:object:) 支持 Combine publisher。Publisher 将每个通知转换为 Combine 事件,可以通过 map、filter、debounce 和 throttle 进行转换。这解决了同步传递的问题:Combine 在指定的 Scheduler 上异步处理通知。NotificationCenter.publisher — 是 legacy 机制和现代响应式编程之间的桥梁。
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 对于从任何线程调用 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 — 观察特定对象的特定属性的变化。KVO 需要继承 NSObject,并在属性通过 setter 更改时自动通知。NotificationCenter 仅在显式调用 post 时通知。对于模型观察,KVO 或 Combine 更受青睐。
每个应用程序进程一个 default center。可以通过 NotificationCenter() 创建额外的中心,但实践中使用共享的 default。每个中心独立工作 — 一个中心中的 post 不会传递到另一个中心的观察者。对于模块隔离,请使用通过反向域名通知名称创建的单独的 Name 命名空间。
部分取代。Combine 提供了 NotificationCenter.Publisher,它将 NotificationCenter 包装在响应式流中。Combine 解决了同步问题(通过 receive(on:)),添加了转换操作符和自动订阅管理(AnyCancellable)。然而,NotificationCenter 仍然用于 iOS 系统通知(UIApplication、UIKeyboard)和 legacy 代码。Combine 是一个附加层,而不是替代品。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。