Foundation — NotificationCenter

1. 什么是 NotificationCenter? #

NotificationCenter 是 iOS/macOS 中的一个 发布/订阅通知机制,用于在应用程序运行时实现不同对象之间的松耦合通信。通过 NotificationCenter,您无需直接引用目标对象,而是通过添加观察者、发布通知的方式,向全局共享的 NotificationCenter 注册观察者,并当广播通知时触发相应的操作。

主要特点 #

  • 发布/订阅模式: 允许对象通过订阅接收事件(通知);发布者不需要知道谁会处理通知,处理者也不需要知道谁发送了通知。
  • 松耦合: 两个对象间无需直接交互,可以完全独立,只通过 NotificationCenter 作为中间人进行沟通。
  • 全局共享实例: 系统提供了一个默认的共享实例 NotificationCenter.default,大多数情况下可以直接使用。

2. NotificationCenter 的组成与体系结构 #

NotificationCenter 的体系结构由以下部分组成:

2.1 通知中心(NotificationCenter) #

这是核心部分,负责管理通知机制的工作:

  • 添加和移除观察者(监听者);
  • 发出(广播)通知给观察者。

API 入口:

  • NotificationCenter.default 为共享实例,全局可用。
  • 你也可以通过 NotificationCenter() 创建自己的实例来实现独立的通知机制。

2.2 通知(Notification) #

封装了事件的对象,它是一个 Notification 类型的结构,包含以下内容:

  • 通知的 name(标识事件)的唯一标志(Notification.Name 类型)。
  • 一个可选的 userInfo(键值对)字典,用于传递附加信息。
  • 通知发送者(object),可以指定触发通知的对象。

创建通知: 通知本质是通过 Notification 对象封装的:

let notification = Notification(name: .someNotificationName, object: someObject, userInfo: ["key": "value"])

2.3 通知名字(Notification.Name) #

通知的标识符,类型为 Notification.Name,表示一个通知事件的名称。推荐使用扩展来定义所有通知名称,集中管理。

extension Notification.Name {
    static let userDidLogin = Notification.Name("userDidLogin")
    static let dataDidUpdate = Notification.Name("dataDidUpdate")
}

3. 如何使用 NotificationCenter #

3.1 基本步骤: #

NotificationCenter 的使用一般分以下三步:

  1. 定义通知名称:

    • 使用 Notification.Name 定义一个全局的通知名称,便于发布者和观察者都知道事件名称。
    extension Notification.Name {
        static let someNotification = Notification.Name("someNotification")
    }
    
  2. 监听通知(订阅者):

    • 在需要接收通知的对象中,添加监听器,用 NotificationCenter.default.addObserver() 注册感兴趣的通知。
    NotificationCenter.default.addObserver(self, 
                                           selector: #selector(handleNotification(_:)), 
                                           name: .someNotification, 
                                           object: nil)
    
    • 监听器 selector 方法使用 @objc 修饰,并接受一个 Notification 参数:
      @objc func handleNotification(_ notification: Notification) {
          print("Notification received: \(notification)")
      }
      
  3. 发送通知(发布者):

    • 在需要触发事件的地方,通过 NotificationCenter.default.post() 广播通知。
    NotificationCenter.default.post(name: .someNotification, object: nil, userInfo: ["key": "value"])
    

3.2 使用示例 #

案例:用户登录事件广播

  1. 定义通知名称
extension Notification.Name {
    static let userDidLogin = Notification.Name("userDidLogin")
}
  1. 发布者:广播通知
class LoginManager {
    func loginUser() {
        print("User has logged in!")
        // 发布通知
        NotificationCenter.default.post(name: .userDidLogin, object: self, userInfo: ["username": "JohnSmith"])
    }
}
  1. 观察者:接收并响应通知
class HomePageViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // 添加用户登录通知监听
        NotificationCenter.default.addObserver(self, 
                                               selector: #selector(handleUserLogin(_:)), 
                                               name: .userDidLogin, 
                                               object: nil)
    }

    @objc func handleUserLogin(_ notification: Notification) {
        if let userInfo = notification.userInfo,
           let username = userInfo["username"] as? String {
            print("User logged in: \(username)")
        }
    }

    // 一定记得移除监听,防止内存泄漏
    deinit {
        NotificationCenter.default.removeObserver(self, name: .userDidLogin, object: nil)
    }
}

4. 通知中心的高级功能 #

4.1 过滤特定通知发送者 #

  • 可以设置 object 来过滤特定对象的通知。
  • addObserverpostobject 匹配时,通知才会发送。
NotificationCenter.default.addObserver(self, 
                                       selector: #selector(handleNotification(_:)), 
                                       name: .someNotification, 
                                       object: specificObject) // 仅接收 `specificObject` 发送的通知

发送方:

NotificationCenter.default.post(name: .someNotification, object: specificObject)

4.2 手动移除通知观察者 #

  • 在不再需要接收通知时,手动移除观察者:
NotificationCenter.default.removeObserver(self, name: .someNotification, object: nil)
  • 如果不指定 nameobject,会移除监听的所有通知:
NotificationCenter.default.removeObserver(self)

4.3 使用 Combine 替代 Observer #

iOS 13+ 中,Combine 框架提供了一种更现代的声明式方法,替代 addObserver

import Combine

class HomePageViewController: UIViewController {
    var cancellables: [AnyCancellable] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        NotificationCenter.default.publisher(for: .userDidLogin)
            .sink { notification in
                if let userInfo = notification.userInfo,
                   let username = userInfo["username"] as? String {
                    print("User logged in: \(username)")
                }
            }
            .store(in: &cancellables)
    }
}

5. 什么时候使用 NotificationCenter? #

合适场景 #

  1. 对象间松耦合通信: 当多个对象需要相互通信,但彼此不应直接引用(强耦合)时,NotificationCenter 是一个强大的工具。例如:

    • 全局事件通知:
      • 用户登录、注销、数据更新。
    • 模块间通信: 将通知作为模块间传递数据与事件的桥梁,比如 ViewController 与后台数据模块之间通信。
  2. 事件广播/全局通知:

    • 一次性向多个对象发送消息。
    • 比如,当主题或语言发生更改时,更新整个应用的 UI 样式。
  3. 响应特定系统事件: 系统使用 NotificationCenter 广播了一些重要事件,比如:

    • 键盘事件: NSNotification.Name.UIKeyboardWillShowUIKeyboardDidHide
    • 设备旋转事件: UIDevice.orientationDidChangeNotification
  4. 数据更新事件: 当数据模型或配置发生变化时,通知所有依赖它的模块或页面刷新数据。


不适用场景 #

  1. 需要保证事件响应顺序: NotificationCenter 不保证通知是按顺序到达的,如果需要严格的顺序控制,使用 回调函数代理模式 更适合。

  2. 单点对单点通信: 如果只有一个对象需要知道事件的发生,使用 闭包Delegate(代理模式) 会更清晰。

  3. 跨进程通知: 需要用于多进程间通信时,可考虑使用其他机制。


6. 常见注意事项 #

  1. 移除观察者内存泄漏

    • 使用 NotificationCenter.default.addObserver 后,必须在适当时机移除监听,否则会造成内存泄漏。
    • 尤其是在 deinit 中确保观察者被移除:
      deinit {
          NotificationCenter.default.removeObserver(self)
      }
      
  2. 避免名字冲突: 使用 Notification.Name 扩展来避免不同模块使用相同的通知名字导致冲突。

  3. 线程安全:

    • NotificationCenter 的所有功能是线程安全的,但通知接收者的代码必须自己确保在主线程上更新 UI:
      DispatchQueue.main.async {
          // 更新 UI 逻辑
      }
      

总结 #

NotificationCenter 优点:

  • 简单、灵活,适合多对象解耦。
  • 广播机制强大,支持全局与特定事件的监听。
  • 系统级通知支持丰富。

典型使用场景:

  • 全局事件广播:如登录事件、数据刷新。
  • 轻量级模块间通信:不需要严格绑定对象。

其他选择: 对于现代 Swift 项目,可以结合 Combine 框架 或替代设计模式(如 Delegate/闭包/回调函数)来提升代码的可读性和可靠性。

本文共 2112 字,上次修改于 Jan 8, 2025