You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5协议中使用@objc的报错问题及NotificationCenter观察者移除方案咨询

解决Swift @objc协议扩展中Selector的问题,以及正确管理Notification观察者

我来帮你搞定这个问题,你遇到的核心困境在于Swift协议扩展里的默认方法无法直接标记@objc,而#selector又要求目标方法能被Objective-C识别。同时你尝试的block方案因为没保存观察者token导致无法正确移除,下面给你两个可行的解决方案:

方案一:修正@objc协议的方法声明

问题的根源是trackScreenshot是协议扩展里的默认实现,并没有被声明为@objc协议的成员,所以Objective-C运行时找不到它。我们可以把这个方法加到@objc协议中,确保它能被Selector识别:

import UIKit

// 让协议继承NSObjectProtocol,确保遵守的类是NSObject子类
@objc protocol TrackScreenshot: NSObjectProtocol {
    func registerObserver()
    func removeObservers()
    @objc func trackScreenshot() // 将方法加入协议并标记@objc
}

extension TrackScreenshot where Self: ScreenTracking {
    func registerObserver() {
        NotificationCenter.default.addObserver(self, selector: #selector(trackScreenshot), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
    }
    
    func removeObservers() {
        NotificationCenter.default.removeObserver(self, name: UIApplication.userDidTakeScreenshotNotification, object: nil )
    }
    
    // 这里的默认实现会被遵守协议的类继承
    func trackScreenshot() {
        print(screenName.rawValue)
    }
}

注意事项:

  • 所有遵守TrackScreenshot的类必须是NSObject的子类(因为协议继承了NSObjectProtocol)
  • 如果某个子类需要自定义trackScreenshot逻辑,直接在类里重写即可,Swift会优先调用类的实现

方案二:使用Token管理Block式观察者(更推荐)

这个方案完全避开@objc的限制,利用NotificationCenter.addObserver(forName:...)返回的NSObjectProtocol token来管理观察者的生命周期,完美解决你之前无法移除观察者的问题:

import UIKit

protocol TrackScreenshot: ScreenTracking {
    // 添加一个属性来保存观察者token
    var screenshotObserverToken: NSObjectProtocol? { get set }
    func registerObserver()
    func removeObservers()
}

extension TrackScreenshot {
    func registerObserver() {
        // 保存token,后续用于移除
        screenshotObserverToken = NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: nil) { [weak self] _ in
            guard let self = self else { return }
            print(self.screenName.rawValue)
        }
    }
    
    func removeObservers() {
        if let token = screenshotObserverToken {
            NotificationCenter.default.removeObserver(token)
            screenshotObserverToken = nil // 清空token,避免野指针
        }
    }
}

为什么这个方案更好?

  1. 不需要任何@objc修饰,完全基于Swift语法,避免了Objective-C兼容性问题
  2. 通过token精准移除当前屏幕的观察者,不会出现旧观察者残留的情况
  3. 使用[weak self]避免了循环引用,保证内存安全

你只需要让你的屏幕类遵守TrackScreenshot协议,并实现screenshotObserverToken属性(比如在类里声明var screenshotObserverToken: NSObjectProtocol?),然后在屏幕的生命周期方法(比如viewWillAppear/viewWillDisappear)里调用registerObserver()和removeObservers()就可以了。

内容的提问来源于stack exchange,提问作者Jkrist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 20:07:49