swift协议扩展的呼叫层次结构问题
我已经为Alamofire的可达性实现了一个小包装器。现在我遇到了一个问题,一个子类没有收到通知 正如dfri在注释中添加的那样,这个问题可以描述为:如果子类实例调用的超类方法被放置在扩展中,则它看不到被重写的子类方法 感谢他出色的要点,显示了常见foo/bar的重现性问题: 协议和扩展逻辑:swift协议扩展的呼叫层次结构问题,swift,protocols,Swift,Protocols,我已经为Alamofire的可达性实现了一个小包装器。现在我遇到了一个问题,一个子类没有收到通知 正如dfri在注释中添加的那样,这个问题可以描述为:如果子类实例调用的超类方法被放置在扩展中,则它看不到被重写的子类方法 感谢他出色的要点,显示了常见foo/bar的重现性问题: 协议和扩展逻辑: protocol ReachabilityProtocol: class { func reachabilityDidChange(online: Bool) } extension R
protocol ReachabilityProtocol: class {
func reachabilityDidChange(online: Bool)
}
extension ReachabilityProtocol {
func configureReachabilityManager() {
// triggered externally:
rechabilityManager?.listener = { [weak self] status in
self?.reachabilityDidChange(online)
}
}
func reachabilityDidChange(online: Bool) {
// abstract, implement in subclasses
}
}
现在ViewControllerA采用此协议并实现以下方法:
class A: UIViewController, ReachabilityProtocol {
func reachabilityDidChange(online: Bool) {
debugPrint("123")
}
}
工作到这里,打印123
ViewControllerB是一个子类,覆盖了a中的方法:
class B: A {
override func reachabilityDidChange(online: Bool) {
super.reachabilityDidChange(online)
debugPrint("234")
}
}
class B: A {
...
}
// MARK: - Reachability Notification
extension B {
override func reachabilityDidChange(online: Bool) {
super.reachabilityDidChange(online)
debugPrint("234")
}
}
我也在工作。通知后立即打印234
和123
现在棘手的是,在代码结构方面,重写的方法被移动到B内部的一个自己的扩展名中(与类B相同的swift文件):
现在不再调用B的reachabilityDidChange()
。输出仅为123
为了检查逻辑,我甚至试图删除覆盖
关键字:编译器立即抱怨需要它
因此,我可以说,调用层次结构是正确的。能见度也很高
包容:如果覆盖的方法被放入它自己的扩展中,它就不再可见/被调用,但编译器仍然需要override
关键字。
这是我还没有遇到的事情,或者是我今天在同一个项目上工作太久
有什么提示吗?在扩展中重写超类方法:仅允许与Objective-C兼容的方法
首先,我们注意到,如果方法与Objective-C兼容,则只能在子类的扩展中重写超类方法。对于从NSObject
派生的类,这对于所有实例方法都是正确的(这里是正确的,因为UIViewController
派生自NSObject
)。这包括在以下问答中:
通过Objective-C运行时实现动态调度 现在,我们注意到以下几点 当Objective-C运行时导入Swift API时,没有 对属性、方法、下标或 初始化者。Swift编译器仍然可以进行设备化或内联 成员访问以优化代码的性能,绕过 Objective-C运行时 您可以使用
dynamic
修饰符要求对成员的访问权限为
通过Objective-C运行时动态调度
而且,从我们读到
动态
(修改器)
将此修饰符应用于可以表示的类的任何成员
按Objective-C。当您使用dynamic
修饰符,对该成员的访问总是使用
Objective-C运行时。对该成员的访问从来都不是内联的或非内联的
由编译器进行虚拟化
因为使用动态
修饰符标记的声明被调度
使用Objective-C运行时,它们被隐式标记为objc
属性
对中的可达性idchange(…)
强制执行动态调度
因此,如果在超类A
中向方法reachabilityDidChange(…)
添加dynamic
修饰符,那么对reachabilityDidChange(…)
的访问将始终使用Objective-C运行时动态调度,从而找到并使用正确的重写reachabilityDidChange(…)
类B
扩展中的方法,用于B
的实例。因此,
dynamic func reachabilityDidChange(online: Bool) { ... }
在中,A
将修复上述问题
下面是上述问题的一个更简单的示例,通过要求通过obj-c运行时对类a
中的方法foo()
(相当于类a
中的方法reachabilityDidChange(…)
)进行动态调度来弥补
导入UIKit
协议Foo:class{
func foo()
}
分机{
func foo(){print(“default”)}
}
A类:UIViewController,Foo{
dynamic func foo(){print(“A”)}//回答得很好,非常感谢@dfri!虽然我基本上了解动态分派和静态分派,但给定的代码在提交之前通过了代码检查:无声地“失败”(由于优化,在逻辑上是正确的)但是要求关键字覆盖
是一件危险的事情:尽管我需要对领域属性等使用动态调度,但我忽略了它与UIViewController超类子类扩展的结合。看起来SwiftBond的作者也遇到了一些调度问题(但不是完全相同的问题):
import UIKit
protocol Foo: class {
func foo()
}
extension Foo {
func foo() { print("default") }
}
class A: UIViewController, Foo {
dynamic func foo() { print("A") } // <-- note dynamic here
func bar() { self.foo() }
/* \
hence, foo() is dynamically dispatched here, and for
instances where "self === B()", this is correctly
resolved as the foo() in the extension to B */
}
class B : A { }
extension B {
override func foo() {
super.foo()
print("B")
}
}
let a = A()
a.bar() // A
let b = B()
b.bar() // A B
/* or, only "A" if not using dynamic dispatch */