Swift中UINavigationController子类调用父类navigationBar(_:shouldPop:)方法的实现方案
失败方案的核心原因
OC版本代码能正常运行,本质是依托UINavigationController本身就是自身navigationBar默认delegate、且已经内置实现navigationBar:shouldPopItem:方法的特性,子类重写该方法后调用super就是正常走父类的内置流程,没有delegate链路的额外问题。
Swift编译模型对这部分系统实现做了隐藏处理,才导致两次尝试全部失效:
- 直接调用
super.navigationBar(_:shouldPop:)报编译错误:Swift编译器没有把这个系统内部使用的delegate方法标记为可直接override的公开接口,调用super时找不到对应方法的声明。IDE自动补全添加的override关键字是误判,本质编译器根本不认为这个方法是父类提供的可重写成员。 - 用
super.perform(...)触发无限循环:runtime的方法查找流程会优先命中子类已经实现的同名selector,根本不会跳转至父类的原始实现,所以一调用就会重复触发自己写的方法,形成死循环。
正确实现方案
不需要在extension里额外声明遵守UINavigationBarDelegate,通过方法交换+保存原始IMP的方式,等价实现OC里的重写+super调用逻辑,可直接运行的参考代码:
final class TestNavigationController: UINavigationController { // 定义和原始方法完全匹配的C函数类型,用来安全调用原始IMP private typealias ShouldPopAction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool // 提前获取UINavigationController内置的原始方法IMP private static let originalMethodIMP: IMP = { let originalSel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:)) guard let method = class_getInstanceMethod(UINavigationController.self, originalSel) else { fatalError("找不到UINavigationController的shouldPop原始方法") } return method_getImplementation(method) }() // 把原始IMP转成可直接调用的闭包 private lazy var callOriginalShouldPop: ShouldPopAction = { return unsafeBitCast(Self.originalMethodIMP, to: ShouldPopAction.self) }() override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) swizzleShouldPopMethod() } required init?(coder: NSCoder) { super.init(coder: coder) swizzleShouldPopMethod() } // 方法交换:把自定义实现挂到系统的selector上 private func swizzleShouldPopMethod() { let originalSel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:)) let customSel = #selector(TestNavigationController.custom_navigationBar(_:shouldPop:)) guard let originalMethod = class_getInstanceMethod(Self.self, originalSel) ?? class_getInstanceMethod(UINavigationController.self, originalSel), let customMethod = class_getInstanceMethod(Self.self, customSel) else { return } method_exchangeImplementations(originalMethod, customMethod) } // 自定义的拦截逻辑 @objc private func custom_navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { // 在这里写你自己的业务拦截逻辑,比如判断是否允许返回 // ... // 调用原始的系统实现,完全等价于OC里的 [super navigationBar:navigationBar shouldPopItem:item] return callOriginalShouldPop( self, #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:)), navigationBar, item ) } }
实现注意事项
- 不要通过extension额外声明遵守
UINavigationBarDelegate,系统已经自动将UINavigationController设为自身导航栏的delegate,额外声明没有实际作用,反而可能打乱内置的delegate调用链路。 - 自定义的方法签名要和系统原始方法完全匹配:不要给
navigationBar参数添加可选标记,不要强制加自动补全带的override关键字,否则会因为签名不一致导致runtime识别不到对应方法,自定义逻辑完全失效。 - 不需要保留之前的异步pop临时实现,这套基于IMP交换的方案可以完全对齐OC版本的原生逻辑,不会衍生额外的栈错乱问题。
内容的提问来源于stack exchange,提问作者Rakuyo
相关产品推荐
相关产品推荐

