iOS 本地通知回调时自定义协议delegate已设置却仍为nil
问题核心成因
出现delegate莫名为nil的根本原因是:收到系统通知回调的LocalNotification实例,和你之前赋值过delegate的实例,根本不是同一个对象。
具体逻辑链条如下:
- 你在
FinalDecisionViewController的setupNotification方法中,通过localNotification = LocalNotification()创建了实例A,给实例A的delegate属性赋值为当前页面控制器,这一步流程是正常的,所以didSet回调可以正常打印was set。 - 但你的代码从未将实例A设置为
UNUserNotificationCenter的delegate。系统收到通知点击事件时,只会调用它自身持有的LocalNotification实例(大概率是你在AppDelegate或应用启动流程中初始化的全局实例B)的通知回调方法。 - 实例B从未被设置过
NavigationProtocol类型的delegate,属性默认值为nil,自然无法触发对应的协议方法。 - 你之前通过deinit日志排查时,观察的是被页面持有的实例A的释放状态,只要页面控制器没被弹出,实例A就会一直存活,因此你会误以为收到回调的是同一个实例,完全没意识到实例B的存在。
排查步骤
- 给所有
LocalNotification实例添加内存地址打印:- 在设置delegate的位置打印
localNotification的内存地址、delegate指向的对象内存地址 - 在
userNotificationCenter(_:didReceive:withCompletionHandler:)回调方法中打印当前self的内存地址 - 对比两处打印的内存地址,大概率会发现两个值完全不一致。
- 在设置delegate的位置打印
- 全局搜索
UNUserNotificationCenter.current().delegate的赋值位置,确认系统通知中心实际持有的delegate实例是哪一个,排查是否存在多个LocalNotification实例初始化的逻辑。
解决方法
- 将
LocalNotification改造为单例模式,保证全局只有一个实例,所有通知回调都由该实例统一处理:class LocalNotification: NSObject, UNUserNotificationCenterDelegate { // 全局单例 static let shared = LocalNotification() weak var delegate: NavigationProtocol? { didSet { print("was set") } } private override init() { super.init() // 初始化时直接将自身设置为通知中心的delegate,避免回调偏移 UNUserNotificationCenter.current().delegate = self } // 保留原有通知调度、回调方法实现 } - 业务页面移除本地实例化
LocalNotification的逻辑,直接使用单例完成delegate绑定和通知调度:private func setupNotification() { // 移除原有本地实例化代码 LocalNotification.shared.delegate = self LocalNotification.shared.scheduleNotification(title: "Loan Approval", message: "Congratulations! Your loan has been approved", type: "Loan Approval") } - 补充生命周期匹配逻辑:由于delegate是weak修饰,在页面控制器的
deinit方法中主动将单例的delegate置为nil,避免产生悬空引用。
补充注意点:必须在应用启动流程(如AppDelegate的
didFinishLaunchingWithOptions方法)中完成LocalNotification单例的初始化,否则当应用被系统杀死后,收到通知唤起进程时,系统将无法找到对应的通知回调delegate。
内容的提问来源于stack exchange,提问作者user7556668
相关产品推荐
相关产品推荐

