iOS通知机制NotificationCenter
iOS通知机制NotificationCenter

iOS通知机制NotificationCenter

NotificationCenter 是 iOS/macOS 开发中的发布-订阅模式(Publish-Subscribe)的通知机制,用于在应用内的不同部分之间传递消息。

允许任意对象向中心发布通知(Notification),并允许任意对象按条件订阅这些通知。适用于监听系统级别或自定义的事件(如键盘弹起、iCloud 数据变化等)。

基本概念

NotificationCenter可以理解为校园广播站,该系统分为三个角色:

1、发送者(Poster):比如校长,它拿着麦克风喊话,但并不清楚谁在听广播,也不认识具体的学生,只是一个宽泛的广播。

2、通知中心(NotificationCenter):校园广播站的达大喇叭,负责将声音传播出去。

3、接收者(Observer):比如学生,它们可以收听广播,可以自主选择收听特定的频道(收听下课铃声,不收听失物招领)。

NotificationCenter特点:

一对多的关系,校长(发送者)的广播可以让非常多的学生(接收者)收听。

两者关系解耦,校长(发送者)不需要认识全校学生,只需要将广播发出去即可。学生(接收者)可以选择收听,也可以不选择收听。

基本用法

1、定义通知名称

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

定义通知名称,可以避免手写字符串出错的问题。

2、发送通知

// 1. 最简单的发送(只发信号)
NotificationCenter.default.post(name: .downloadCompleted, object: nil)

// 2. 携带数据的发送
let info: [String: String] = ["fileName": "photo.jpg"]
NotificationCenter.default.post(name: .downloadCompleted, object: nil, userInfo: info)

// 3. 指定发送者的发送(很少用,除非为了区分是谁发的)
NotificationCenter.default.post(name: .downloadCompleted, object: self)

使用NotificationCenter.default.post可以将通知发送给任何监听 .downloadCompleted的对象。

object表示通知来源,nil表示不提供发布者身份,也可以设置为 self。

3、监听通知

iOS提供多种监听方式:Selector模式、Closure模式和SwiftUI的onReceive方法,这也是比较难理解的地方。

1、旧方法:Selector模式(传统模式)

Objective-C 语言中,Selector模式的特点是逻辑分离、需要配合 @objc 方法使用,不能指定线程(和通知发布者线程保持一致),在 iOS 9之前,在对象销毁后需要手动注销监听。

// 注册监听
NotificationCenter.default.addObserver(
    self,                            // 观察者,当前类/对象
    selector: #selector(handleNotif), // 监听后调用的方法
    name: .downloadCompleted,        // 监听的频道/通知
    object: nil                      // 监听的对象
)

1)self表示观察者,类型为 Any,表示谁来响应通知(通常是self)。

2)selector表示响应通知时调用的方法,类型为Selector,因为 Selector 是 Objective-C 的特性,该方法必须带 @objc 修饰。

@objc func handleNotif(_ notification: Notification) {
    // notification.userInfo 可以取附加数据
    print("下载完成!")
}

3)name表示监听的通知,类型为Notification.Name?,比如.downloadCompleted 是前面定义的通知名称,当该通知广播时,可以监听并响应。

4)object表示指定监听的对象,类型为Any?,nil表示监听所有对象发出的通知,如果指定对象,则只监听这个对象发出的通知。

使用场景:在类的构造方法中注册监听,当监听到广播时调用handleNotif方法获取数据,当对象销毁时移除监听。

class OldObserver {
    init() {
        // 注册监听
        NotificationCenter.default.addObserver(
            self,                            // 观察者,当前类/对象
            selector: #selector(handleNotif), // 监听后调用的方法
            name: .downloadCompleted,        // 监听的频道/通知
            object: nil                      // 监听的对象
        )
    }

    // 必须加 @objc
    @objc func handleNotif(_ notification: Notification) {
        // 获取监听的数据
        if let info = notification.userInfo, let fileName = info["fileName"] as? String {
            print("旧方法收到:\(fileName) 下载完毕")
        }
    }
    
    deinit {
        // 移除监听,防止内存溢出
        NotificationCenter.default.removeObserver(self)
    }
}
2、新方法:Closure模式

这是Swift风格的写法,不需要使用 @objc ,代码紧凑,可以指定执行线程。

它返回一个 NSObjectProtocol 对象,当该对象销毁时,也需要移除监听,防止内存泄漏。

NotificationCenter.default.addObserver(
    forName: .downloadCompleted, // 监听的频道/通知
    object: nil,                 // 监听的对象
    queue: .main                 // 指定的执行线程
) { [weak self] notification in  // 闭包
    if let info = notification.userInfo {
        print("新方法收到数据:\(info)")
    }
}

queue表示收到通知后,类型为OperationQueue?,指定处理通知的线程。nil 表示在后台线程或发布者的线程上运行,.main 表示在主线程上运行。

使用场景:在类中保存返回的 NSObjectProtocol 对象,当移除对象时,移除监听。

class NewObserver {
    // 持有 NSObjectProtocol 对象的变量
    var observerToken: NSObjectProtocol?

    init() {
        // 注册监听
        observerToken = NotificationCenter.default.addObserver(
            forName: .downloadCompleted, // 监听的频道/通知
            object: nil,                 // 监听的对象
            queue: .main                 // 指定的执行线程
        ) { [weak self] notification in  // 闭包
            // 注意:因为是在闭包里,使用 self 最好加 [weak self] 避免循环引用
            if let info = notification.userInfo {
                print("新方法收到数据:\(info)")
            }
        }
    }

    deinit {
        // 移除对象时,同步移除监听
        if let token = observerToken {
            NotificationCenter.default.removeObserver(token)
        }
    }
}
3、SwiftUI专用方法:Publisher

在SwiftUI视图中,通常不使用上述两种方法,而是使用 onReceive 方法监听通知。

.onReceive(
    NotificationCenter.default.publisher(
        for: NSUbiquitousKeyValueStore.didChangeExternallyNotification
    )
) { notification in
    let reason = notification.userInfo?[NSUbiquitousKeyValueStoreChangeReasonKey]
    print("iCloud 数据发生变化")
}

onReceive通过 NotificationCenter.default.publisher 监听发布者。

该方法的优势在于,不需要手动管理生命周期,视图销毁时自动停止监听。

使用场景:创建发布者,SwiftUI视图添加 onReceive 修饰符,当监听到通知时,获取数据。

import SwiftUI

struct MyView: View {
    // 1. 创建发布者
    let downloadPub = NotificationCenter.default.publisher(for: .downloadCompleted)

    @State private var textName = "waiting for name ···"

    var body: some View {
        Text(message)
            // 2. 接收通知
            .onReceive(downloadPub) { notification in
                if let info = notification.userInfo, let name = info["fileName"] as? String {
                    textName = name
                }
            }
    }
}

这里使用 NotificationCenter.default.publisher 将通知系统包装成一个 Combine 的Publisher(发布者)。

NotificationCenter.default.publisher(for: .downloadCompleted)

也可以包装其他的监听对象,例如NSTextView对象:

.onReceive(NotificationCenter.default.publisher(for: NSTextView.didChangeSelectionNotification)) { obj in
    if let textView = obj.object as? NSTextView {
        print("macOS: 正在输入拼音 (Marked Text)...")
        scrollToEnd(proxy)
    }
}

注意事项

1、移除监听:如果接收者被销毁,没有移除监听,可能会导致内存泄漏或崩溃。

在iOS 9+版本中,普通监听通常会自动注销,但仍然建议在deinit中显式移除监听:

deinit {
    NotificationCenter.default.removeObserver(self)
}

2、使用建议:在不同的场景中选择不同的监听通知方法。

在SwiftUI视图中监听,使用SwiftUI专用的 onReceive 方法。

在Swift类中,简单的监听可以使用旧方法(Selector),如果需要使用上下文中的变量或者需要在主线程中运行,则使用新方法(Closure)。

总结

NotificationCenter 适用于全局事件监听,可适用于监听iCloud 数据变化、用户登录状态变化、App 进入后台或前台、设备旋转、键盘弹起等系统事件。

在实际应用中,可以通过监听来解决ScrollView中的TextField输入拼音无法响应、监听iCloud数据变化并同步数据等问题。

可以把它理解成广播机制,多个对象可以订阅同一个事件,当事件发生时,所有订阅者都会收到通知。

相关文章

1、SwiftUI监听iCloud数据变化:https://fangjunyu.com/2025/12/17/swiftui%e7%9b%91%e5%90%acicloud%e6%95%b0%e6%8d%ae%e5%8f%98%e5%8c%96/

2、SwiftUI ScrollView中的TextField无法监听拼音输入的问题:https://fangjunyu.com/2025/12/17/swiftui-scrollview%e4%b8%ad%e7%9a%84textfield%e6%97%a0%e6%b3%95%e7%9b%91%e5%90%ac%e6%8b%bc%e9%9f%b3%e8%be%93%e5%85%a5%e7%9a%84%e9%97%ae%e9%a2%98/

3、SwiftUI响应Combine的onReceive:https://fangjunyu.com/2025/07/15/swiftui%e5%93%8d%e5%ba%94combine%e7%9a%84onreceive/

4、Apple处理异步任务的Combine框架:https://fangjunyu.com/2024/12/01/apple%e5%a4%84%e7%90%86%e5%bc%82%e6%ad%a5%e4%bb%bb%e5%8a%a1%e7%9a%84combine%e6%a1%86%e6%9e%b6/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注