NSNotificationCenter(NotificationCenter)官方文档通俗解读、API 详解与 Swift 实战
英文标题:NSNotificationCenter(NotificationCenter)官方文档白话翻译 + API 详解 + Swift 实战
NotificationCenter 是 App 进程内的发布—订阅式消息分发机制,发送方无需知道接收方,多个观察者可订阅同一通知。文章梳理了 selector 与 block 两套观察者 API、移除观察者方式,以及携带 `object` 和 `userInfo` 的通知发送方法。selector 方式在 iOS 9 及以后会随观察者对象销毁自动移除,而 block 方式必须保存返回的 token 并手动移除,同时应使用 `[weak self]` 避免循环引用。对于开发者而言,通知适合系统事件和多模块广播同步,但不适合一对一通信或需要返回值的场景,并需注意回调线程和 `post` 的同步阻塞特性。
9.2 KB
中文
文档来源 Apple:
NotificationCenter | Apple Developer Documentation本质:发布—订阅(广播)机制。同一个 App 内部发送消息时,发送方无需知道谁会接收;接收方只要订阅消息即可收到。
⚠️ 只能在同一个 App 进程内通信;跨进程需要使用
NSDistributedNotificationCenter(仅 macOS 可用,iOS 不支持跨进程通知)。
总览翻译(Overview)
NotificationCenter 是一种消息分发机制,用于向已经注册的观察者广播消息。
-
Objective-C / 旧 API:注册并接收
NSNotification对象消息,有两种注册方式:addObserver:selector:name:object:方法:回调通过@selector函数执行。addObserverForName:object:queue:usingBlock:方法:回调通过 block 闭包执行。
-
新版 Swift 并发 API:
addObserver(of:for:using:),具备类型安全特性,并适配 Swift Actor 隔离。 -
每个 App 都自带一个全局默认通知中心
defaultCenter;也可以自行创建独立的通知中心,用于分组消息。 -
❗普通通知只能在当前 App 内部收发。macOS 如需在不同程序之间发送消息,应使用
NSDistributedNotificationCenter;iOS 不具备跨进程通知能力。
核心 API 分为 4 大类
一、获取通知中心对象
1. defaultCenter(Swift:NotificationCenter.default)
作用:获取 App 全局默认通知中心,99% 的业务场景都使用它。
无需自行创建,这是一个全局单例。
let center = NotificationCenter.default
使用场景:监听 App 前后台状态、键盘弹出、登录成功广播、多页面同步刷新 UI。
前面监听 App 进入后台的写法:
NotificationCenter.default.addObserver(self, selector:#selector(appEnterBg),
name:UIApplication.didEnterBackgroundNotification, object:nil)
这就是通过全局默认通知中心完成注册。
二、添加观察者(两套 API,重点区分常见问题)
API-1 addObserver(_:selector:name:object:):Objective-C 风格 selector 回调
Swift 签名:
func addObserver(_ observer: Any,
selector aSelector: Selector,
name aName: NSNotification.Name?,
object anObject: Any?)
参数说明:
| 参数 | 含义 | 传 nil 的效果 |
|---|---|---|
| observer | 谁要接收通知,一般传入 self |
不能为 nil |
| selector | 收到通知后执行的 @objc 方法 |
不能为 nil |
| name | 通知名称,决定监听哪类消息 | nil:监听全部通知 |
| object | 监听哪个发送者发出的通知 | nil:接收所有发送者发出的该 name 通知 |
✅ Swift 完整示例:
extension Notification.Name {
static let userLoginSuccess = Notification.Name("userLoginSuccess")
}
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(onLoginSuccess(_:)),
name: .userLoginSuccess,
object: nil)
}
@objc private func onLoginSuccess(_ notify: Notification) {
if let dict = notify.userInfo as? [String:Any], let uid = dict["userId"] as? String {
print("登录成功 userId=(uid)")
}
}
}
适用场景
- 控制器监听系统通知,例如进入后台、键盘弹出或收起。
- 简单广播,回调写在独立的
@objc函数中,代码结构较清晰。
⚠️重要特性:iOS 9 之后,通过 selector 方式注册的观察者,在对象销毁时系统会自动移除,无需手动
remove;iOS 9 以前必须在deinit中移除,否则可能崩溃。❗通知回调所在线程 = 发送通知所在线程。如果在子线程
post通知,回调也会在子线程执行;更新 UI 时需要切换至主队列。
API-2 addObserver(forName:object:queue:using:):Block 闭包版本
该方法会返回一个 token 对象(
Any NSObjectProtocol),必须保存 token,并手动 remove。
func addObserver(forName name: NSNotification.Name?,
object obj: Any?,
queue: OperationQueue?,
using block: @escaping (Notification) -> Void) -> Any
参数说明:
queue:指定 block 回调在哪个队列执行。传入.main时,回调会强制在主线程执行,适合更新 UI;传入nil时,与发送通知的线程保持一致。block:收到通知后执行的闭包。- 返回值:observerToken,必须保存,并在销毁时 remove。
✅ Swift 示例:
class ProfileVC: UIViewController {
private var loginObserverToken: Any?
override func viewDidLoad() {
super.viewDidLoad()
loginObserverToken = NotificationCenter.default.addObserver(
forName: .userLoginSuccess,
object: nil,
queue: .main) { [weak self] notify in
guard let self = self else { return }
print("Profile收到登录成功")
}
}
deinit {
if let token = loginObserverToken {
NotificationCenter.default.removeObserver(token)
}
}
}
核心踩坑点:
- block 版本不会自动移除观察者。即使 VC 已销毁,通知中心仍可能持有 token;不执行
remove会留下残留观察者。 - block 内捕获
self时必须使用[weak self],否则可能形成循环引用并导致内存泄漏。 queue传入.main后,可以自动在主线程更新 UI,非常方便。
适用场景
- 不想编写
@objc函数; - 需要强制在主线程执行回调;
- 希望直接在闭包中编写业务逻辑。
三、移除观察者 removeObserver
1. removeObserver(_ observer:)
Objective-C:
- removeObserver:
-
selector 版本:传入注册时的
self,会移除该对象的全部监听。NotificationCenter.default.removeObserver(self)
2. removeObserver(_:name:object:)
仅移除指定
name、指定 sender 的监听,粒度更精确。
NotificationCenter.default.removeObserver(self, name: .userLoginSuccess, object: nil)
block 版本:传入方法返回的 token 对象进行移除,不要传
self!
NotificationCenter.default.removeObserver(loginObserverToken!)
总结:
- selector 注册 →
removeObserver传入self- block 注册 →
removeObserver传入返回的 token
四、发送通知 Post(广播消息)
1. postNotificationName:object:userInfo:(Swift:post(name:object:userInfo:))
最常用的发送方式,可以通过字典携带数据给接收方。
NotificationCenter.default.post(
name: .userLoginSuccess,
object: self, // object:发送者是谁,接收方可以过滤sender
userInfo: ["userId":"10086","userName":"张三"] // 传递附加数据 [AnyHashable:Any]
)
object:标记消息由谁发出。接收方在调用addObserver时,如果object填入该对象,就只会接收来自这个发送者的通知。userInfo:用于传递附加参数,类型为[AnyHashable: Any]。
2. postNotificationName:object:(不携带附加数据)
NotificationCenter.default.post(name: .userLoginSuccess, object: self)
3. postNotification(_ notification: Notification)
先手动构造
Notification对象再发送,较少使用。
let notify = Notification(name: .userLoginSuccess, object:self, userInfo:["id":"123"])
NotificationCenter.default.post(notify)
✨执行特性:**
post是同步阻塞的。**发送通知时,系统会依次执行所有观察者回调,post函数会在所有回调完成后才返回;如果某个观察者回调耗时较长,会阻塞发送方当前线程。
NSDistributedNotificationCenter(文档提到)
仅 macOS 可用,iOS 不能使用!
它的作用是实现跨进程通知,即同一台电脑上不同 App 进程之间广播消息。iOS 受沙盒隔离限制,不支持跨进程通知,无需考虑此 API。
真实业务场景对照
场景 1:App 切到后台、回到前台
系统提供了内置通知名称,可以直接监听:
NotificationCenter.default.addObserver(self, selector:#selector(didBg),
name: UIApplication.didEnterBackgroundNotification, object:nil)
NotificationCenter.default.addObserver(self, selector:#selector(didFg),
name: UIApplication.willEnterForegroundNotification, object:nil)
场景 2:登录成功后,首页和个人中心多个页面同时刷新 UI
- 登录模块登录成功后,
post通知并携带userId; HomeVC、ProfileVC订阅该通知,收到后刷新各自页面。
优势:登录模块无需持有
HomeVC、ProfileVC等控制器,实现模块解耦。
场景 3:键盘弹出、收起时调整输入框位置
系统通知:UIResponder.keyboardWillShowNotification
❌不适合使用通知的场景
- 一对一通信:应优先使用 delegate 或闭包,不要滥用通知;
- 需要返回值:通知是单向广播,接收方无法向发送方返回结果。
selector 与 block 版通知对比表
| 项目 | addObserver selector |
addObserver block(token) |
|---|---|---|
| 回调形式 | @objc 函数 |
闭包 |
| 是否自动释放 | iOS 9+ 自动移除 | 必须手动 remove(token) |
| 线程控制 | 回调线程 = 发送通知线程,需自行切换主线程 | 可通过 queue 参数直接指定 .main |
| 循环引用风险 | 低 | 高,必须使用 [weak self] |
| 推荐场景 | 系统通知、简单监听 | 需要主线程回调、不想写 @objc |
常见踩坑清单
- block 版本忘记保存 token,或忘记
remove,导致残留观察者; - 在子线程
post通知后,回调会在子线程执行,直接更新 UI 可能崩溃;解决方式:注册时将queue设为.main; userInfo用于传递通知附加数据,需确保其中内容满足使用场景所需的类型和生命周期要求;post是同步阻塞操作,某个观察者回调耗时过长会阻塞发送方;- 不要滥用通知。广播链路难以调试,消息过多时不易定位具体发送方。
English / 原文
文档来源 Apple:
NotificationCenter | Apple Developer Documentation本质:发布‑订阅(广播)机制 ,同一个 App 内部发消息,发送方不用知道谁接收;接收方订阅消息即可收到。 ⚠️ 只能同一个 App 进程内通信 ;跨进程要用NSDistributedNotificationCenter(仅 macOS 可用,iOS 不支持跨进程通知)。
总览翻译(Overview)
NotificationCenter 是消息分发机制,用来给已经注册的观察者广播消息。
-
Objective‑C / 老 API:注册接收
NSNotification对象消息,两个注册方式:addObserver:selector:name:object:方法:回调走 @selector 函数addObserverForName:object:queue:usingBlock:方法:回调走 block 闭包
-
新版 Swift 并发 API:
addObserver(of:for:using:),类型安全、适配 Swift Actor 隔离。 -
每个 App 自带一个全局默认通知中心
defaultCenter;也可以自己新建独立通知中心做分组消息。 -
❗普通通知只能本 App 内部收发 。macOS 如果要不同程序之间发消息,要用
NSDistributedNotificationCenter,iOS 没有跨进程通知能力。
核心 API 分 4 大类
一、获取通知中心对象
1. defaultCenter(Swift:NotificationCenter.default)
作用:拿到 App 全局默认通知中心,99% 业务都用这个
不需要自己 new,全局单例。
let center = NotificationCenter.default
使用场景 :监听 App 前后台、键盘弹出、登录成功广播、多页面同步刷新 UI。
你前面写的监听 App 进入后台:
NotificationCenter.default.addObserver(self, selector:#selector(appEnterBg),
name:UIApplication.didEnterBackgroundNotification, object:nil)
就是拿这个全局中心注册。
二、添加观察者(2 套 API,重点区分坑)
API‑1 addObserver(_:selector:name:object:) OC 风格 selector 回调
Swift 签名:
func addObserver(_ observer: Any,
selector aSelector: Selector,
name aName: NSNotification.Name?,
object anObject: Any?)
参数说明:
表格
| 参数 | 含义 | 传 nil 效果 |
|---|---|---|
| observer | 谁要接收通知,一般传self |
不能 nil |
| selector | 收到通知执行的 @objc 方法 | 不能 nil |
| name | 通知名字,监听哪个消息 | nil:监听全部通知 |
| object | 监听哪个发送者发出的通知 | nil:接收所有发送者 的这条 name 通知 |
✅ Swift 完整示例
extension Notification.Name {
static let userLoginSuccess = Notification.Name("userLoginSuccess")
}
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(onLoginSuccess(_:)),
name: .userLoginSuccess,
object: nil)
}
@objc private func onLoginSuccess(_ notify: Notification) {
if let dict = notify.userInfo as? [String:Any], let uid = dict["userId"] as? String {
print("登录成功 userId=(uid)")
}
}
}
适用场景
- 控制器监听系统通知(后台、键盘)
- 简单广播,回调写单独 @objc 函数,代码清晰。
⚠️重要特性: iOS9 之后,selector 方式注册,对象销毁系统自动移除观察者,不用手动 remove ;iOS9 以前必须 deinit 移除否则崩溃。 ❗通知回调线程 = 发送通知所在线程 !如果子线程 post 通知,回调也跑在子线程,更新 UI 要切主队列。
API‑2 addObserver(forName:object:queue:using:) Block 闭包版本
返回一个 token 对象(
Any NSObjectProtocol),必须保存 token,手动 remove!!
func addObserver(forName name: NSNotification.Name?,
object obj: Any?,
queue: OperationQueue?,
using block: @escaping (Notification) -> Void) -> Any
参数:
queue:block 回调在哪个队列执行;传.main→ 回调强制主线程(更新 UI 首选);nil = 和发送通知同一个线程。block:收到通知执行闭包。- 返回值:observerToken,必须存起来,销毁时 remove!!
✅ Swift 示例
class ProfileVC: UIViewController {
private var loginObserverToken: Any?
override func viewDidLoad() {
super.viewDidLoad()
loginObserverToken = NotificationCenter.default.addObserver(
forName: .userLoginSuccess,
object: nil,
queue: .main) { [weak self] notify in
guard let self = self else { return }
print("Profile收到登录成功")
}
}
deinit {
if let token = loginObserverToken {
NotificationCenter.default.removeObserver(token)
}
}
}
坑点重中之重 博客园:
- block 版本不会自动移除观察者 ,哪怕 vc 销毁,通知中心仍然持有 token 对象,不 remove 会残留;
- block 内部捕获 self 一定要写
[weak self],否则循环引用内存泄漏; - queue 传
.main,自动切主线程做 UI,非常方便。
适用场景
- 不想写
@objc函数; - 需要强制回调主线程;
- 闭包内直接写业务逻辑。
三、移除观察者 removeObserver
1. removeObserver(_ observer:)
OC:
‑ removeObserver:
-
selector 版本:传入当初注册的
self,移除该对象全部监听 。NotificationCenter.default.removeObserver(self)
2. removeObserver(_:name:object:)
只移除指定 name、指定 sender 的监听,更精准。
NotificationCenter.default.removeObserver(self, name: .userLoginSuccess, object: nil)
block 版本:传入返回的 token 对象 移除,不要传 self!
NotificationCenter.default.removeObserver(loginObserverToken!)
总结:
- selector 注册 → removeObserver 传 self
- block 注册 → removeObserver 传返回的 token
四、发送通知 Post(广播出去)
1. postNotificationName:object:userInfo:(Swift:post(name:object:userInfo:))
最常用,可以附带字典数据传给接收方。
NotificationCenter.default.post(
name: .userLoginSuccess,
object: self, // object:发送者是谁,接收方可以过滤sender
userInfo: ["userId":"10086","userName":"张三"] // 传递附加数据 [AnyHashable:Any]
)
object:标记消息是谁发出来;接收方 addObserver 的时候 object 填这个对象,就只会接收来自这个发送者的通知。userInfo:传递参数,只能存引用类型,不能直接放 struct 结构体。
2. postNotificationName:object:(不带附加数据)
NotificationCenter.default.post(name: .userLoginSuccess, object: self)
3. postNotification(_ notification: Notification)
先手动构造 Notification 对象再发送,很少用。
let notify = Notification(name: .userLoginSuccess, object:self, userInfo:["id":"123"])
NotificationCenter.default.post(notify)
✨执行特性:post 是同步阻塞 !发送通知的时候,会挨个执行完所有观察者回调,post 函数才返回;如果某个观察者回调耗时很大,会卡住发送方当前线程。
NSDistributedNotificationCenter(文档提到)
仅 macOS 可用,iOS 不能用! 作用:跨进程通知 ,一台电脑上不同 App 进程之间广播消息。 iOS 沙盒隔离,不支持跨进程通知,不用管。
真实业务场景对照
场景 1:App 切后台、切前台(你上一个问题)
系统自带通知名字,直接监听
NotificationCenter.default.addObserver(self, selector:#selector(didBg),
name: UIApplication.didEnterBackgroundNotification, object:nil)
NotificationCenter.default.addObserver(self, selector:#selector(didFg),
name: UIApplication.willEnterForegroundNotification, object:nil)
场景 2:登录成功,首页、个人中心多个页面同时刷新 UI
- 登录模块登录成功 → post 通知带上 userId;
- HomeVC、ProfileVC 订阅这个通知,收到刷新页面。
优势:登录模块不需要持有 Home、Profile 控制器,解耦。
场景 3:键盘弹出收起,调整输入框位置
系统通知:UIResponder.keyboardWillShowNotification
❌不适合用通知场景
- 一对一通信:优先用 delegate、闭包,不要滥用通知;
- 需要返回值:通知是单向广播,接收方无法给发送方返回结果。
selector vs block 版通知对比表
表格
| 项目 | addObserver selector | addObserver block(token) |
|---|---|---|
| 回调形式 | @objc 函数 | 闭包 |
| 是否自动释放 | iOS9 + 自动移除 | 必须手动 remove (token) |
| 线程控制 | 回调 = 发送通知线程,自己切主线程 | queue 参数可以直接指定.main |
| 循环引用风险 | 低 | 高,必须[weak self] |
| 推荐场景 | 系统通知、简单监听 | 需要主线程回调,不想写 @objc |
常见踩坑清单
- block 版本忘记保存 token,忘记 remove,残留观察者;
- 子线程 post 通知,回调跑在子线程,直接更新 UI 崩溃;解决:注册 queue 传
.main; - userInfo 不能直接传 struct,要包装成 class 对象;
- post 同步阻塞,某个观察者回调卡死会卡住发送方;
- 不要滥用通知,广播调试难,消息满天飞不好定位是谁发的。