文档来源 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,全局单例。
csharp
let center = NotificationCenter.default
使用场景:监听 App 前后台、键盘弹出、登录成功广播、多页面同步刷新 UI。
你前面写的监听 App 进入后台:
php
NotificationCenter.default.addObserver(self, selector:#selector(appEnterBg),
name:UIApplication.didEnterBackgroundNotification, object:nil)
就是拿这个全局中心注册。
二、添加观察者(2 套 API,重点区分坑)
API‑1 addObserver(_:selector:name:object:) OC 风格 selector 回调
Swift 签名:
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 完整示例
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,selector需要
@objc private func onLoginSuccess(_ notify: Notification) {
//读取附带数据 userInfo
if let dict = notify.userInfo as? [String:Any], let uid = dict["userId"] as? String {
print("登录成功 userId=(uid)")
}
}
// iOS9+:selector版本不需要手动removeObserver,对象销毁系统自动清理
// deinit { NotificationCenter.default.removeObserver(self) }
}
适用场景
- 控制器监听系统通知(后台、键盘)
- 简单广播,回调写单独 @objc 函数,代码清晰。
⚠️重要特性: iOS9 之后,selector 方式注册,对象销毁系统自动移除观察者,不用手动 remove ;iOS9 以前必须 deinit 移除否则崩溃。 ❗通知回调线程 = 发送通知所在线程!如果子线程 post 通知,回调也跑在子线程,更新 UI 要切主队列。
API‑2 addObserver(forName:object:queue:using:) Block 闭包版本
返回一个 token 对象(
Any NSObjectProtocol),必须保存 token,手动 remove!!
less
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 示例
swift
class ProfileVC: UIViewController {
//保存token
private var loginObserverToken: Any?
override func viewDidLoad() {
super.viewDidLoad()
loginObserverToken = NotificationCenter.default.addObserver(
forName: .userLoginSuccess,
object: nil,
queue: .main) { [weak self] notify in
// 直接闭包写逻辑,不用@objc函数
guard let self = self else { return }
print("Profile收到登录成功")
}
}
deinit {
// ✅必须手动移除!!block版本不会自动清理!!
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,移除该对象全部监听。
php
NotificationCenter.default.removeObserver(self)
2. removeObserver(_:name:object:)
只移除指定 name、指定 sender 的监听,更精准。
php
NotificationCenter.default.removeObserver(self, name: .userLoginSuccess, object: nil)
block 版本:传入返回的 token 对象移除,不要传 self!
arduino
NotificationCenter.default.removeObserver(loginObserverToken!)
总结:
- selector 注册 → removeObserver 传 self
- block 注册 → removeObserver 传返回的 token
四、发送通知 Post(广播出去)
1. postNotificationName:object:userInfo:(Swift:post(name:object:userInfo:))
最常用,可以附带字典数据传给接收方。
php
//模拟登录成功后发通知
NotificationCenter.default.post(
name: .userLoginSuccess,
object: self, // object:发送者是谁,接收方可以过滤sender
userInfo: ["userId":"10086","userName":"张三"] // 传递附加数据 [AnyHashable:Any]
)
object:标记消息是谁发出来;接收方 addObserver 的时候 object 填这个对象,就只会接收来自这个发送者的通知。userInfo:传递参数,只能存引用类型,不能直接放 struct 结构体。
2. postNotificationName:object:(不带附加数据)
php
NotificationCenter.default.post(name: .userLoginSuccess, object: self)
3. postNotification(_ notification: Notification)
先手动构造 Notification 对象再发送,很少用。
php
let notify = Notification(name: .userLoginSuccess, object:self, userInfo:["id":"123"])
NotificationCenter.default.post(notify)
✨执行特性:post 是同步阻塞!发送通知的时候,会挨个执行完所有观察者回调,post 函数才返回;如果某个观察者回调耗时很大,会卡住发送方当前线程。
NSDistributedNotificationCenter(文档提到)
仅 macOS 可用,iOS 不能用! 作用:跨进程通知,一台电脑上不同 App 进程之间广播消息。 iOS 沙盒隔离,不支持跨进程通知,不用管。
真实业务场景对照
场景 1:App 切后台、切前台(你上一个问题)
系统自带通知名字,直接监听
php
//进入后台
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 同步阻塞,某个观察者回调卡死会卡住发送方;
- 不要滥用通知,广播调试难,消息满天飞不好定位是谁发的。