Swift中如何让父类方法仅可重写不可直接调用?
在Swift中实现父类方法仅允许重写、禁止直接调用的方案
这个问题确实戳中了Swift没有protected访问修饰符的痛点——我们没法直接标记一个方法“只能被子类访问和重写,外部不能调用”,不过结合Swift的访问控制和语言特性,还是有几种靠谱的方案能实现你的需求:
方案1:文件私有钩子(适用于同文件子类)
如果你的子类和父类在同一个源文件里,这是最简单的实现方式:把需要重写的钩子方法标记为fileprivate,父类的final入口方法内部调用它,外部代码因为访问权限限制根本没法直接调用钩子方法,子类则可以正常重写。
class B { // 必须调用的final入口方法,对外暴露 final func execute() { print("父类固定前置逻辑") hookMethod() print("父类固定后置逻辑") } // 仅同文件内可访问的钩子方法,子类可重写 fileprivate func hookMethod() { // 父类默认实现触发崩溃,强制子类重写 fatalError("此方法必须被子类重写,不能直接调用父类实现") } } class A: B { // 子类重写钩子方法 override fileprivate func hookMethod() { print("子类自定义钩子逻辑") } } // 使用示例 let a = A() a.execute() // 正常执行,会触发子类的钩子逻辑 // a.hookMethod() // 编译报错:'hookMethod' is inaccessible due to 'fileprivate' protection level
优点:编译时检查,性能无损耗,实现简单;
缺点:子类必须和父类在同一个文件,跨文件子类无法使用。
方案2:运行时校验钩子(适用于跨文件子类)
如果子类需要和父类在不同文件,我们可以把钩子方法标记为open(允许外部子类重写),然后在父类的默认实现里通过运行时校验,确保这个方法要么被子类重写,要么只能被父类的入口方法调用(不过这里只能做到禁止直接调用父类的未重写实现)。
import Foundation class B { final func execute() { print("父类固定逻辑") hookMethod() } open func hookMethod() { // 校验当前方法是否是父类的原始实现,如果是则崩溃 let selector = #selector(hookMethod) guard let currentMethod = class_getInstanceMethod(type(of: self), selector), let parentMethod = class_getInstanceMethod(B.self, selector) else { fatalError("方法校验失败") } if method_getImplementation(currentMethod) == method_getImplementation(parentMethod) { fatalError("此方法必须被子类重写,不能直接调用父类实现") } } } class A: B { override func hookMethod() { print("子类重写的钩子逻辑") } } // 使用示例 let a = A() a.execute() // 正常执行 a.hookMethod() // 可以直接调用(如果要完全禁止外部调用,这个方案做不到,需要看方案3) B().hookMethod() // 运行时崩溃,因为调用了父类未重写的实现
优点:支持跨文件子类,强制子类重写;
缺点:无法完全禁止外部调用子类的钩子方法,只能在运行时拦截父类的未重写调用。
方案3:代理模式(完全符合需求)
如果想要彻底禁止外部直接调用钩子方法,代理模式是最稳妥的选择——把钩子逻辑抽成私有协议,父类持有代理对象,仅通过final入口方法触发钩子逻辑,外部完全接触不到钩子方法。
// 私有协议,仅当前文件可访问 fileprivate protocol BHookDelegate: AnyObject { func hookMethod() } class B { // 私有代理属性,外部无法访问 private weak var hookDelegate: BHookDelegate? final func execute() { print("父类固定逻辑") hookDelegate?.hookMethod() } // 仅子类可调用的代理设置方法(如果子类在其他文件,可以把这个方法设为open) func setupHookDelegate(_ delegate: BHookDelegate?) { self.hookDelegate = delegate } } class A: B, BHookDelegate { init() { super.init() // 子类初始化时把自己设为代理 setupHookDelegate(self) } // 私有钩子实现,外部完全无法调用 fileprivate func hookMethod() { print("子类代理实现的钩子逻辑") } } // 使用示例 let a = A() a.execute() // 正常执行,触发子类钩子逻辑 // a.hookMethod() // 编译报错:'hookMethod' is inaccessible due to 'fileprivate' protection level
优点:完全禁止外部直接调用钩子方法,支持跨文件子类,逻辑清晰;
缺点:需要额外的协议和代理设置步骤,代码量稍多。
总结
- 同文件子类优先选方案1,简单高效;
- 跨文件子类但允许外部调用子类钩子方法,选方案2;
- 想要彻底禁止外部调用钩子方法,选方案3的代理模式,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Nike Kov
相关产品推荐
相关产品推荐

