swift协议扩展的呼叫层次结构问题

swift协议扩展的呼叫层次结构问题,swift,protocols,Swift,Protocols,我已经为Alamofire的可达性实现了一个小包装器。现在我遇到了一个问题,一个子类没有收到通知 正如dfri在注释中添加的那样,这个问题可以描述为:如果子类实例调用的超类方法被放置在扩展中,则它看不到被重写的子类方法 感谢他出色的要点,显示了常见foo/bar的重现性问题: 协议和扩展逻辑: protocol ReachabilityProtocol: class { func reachabilityDidChange(online: Bool) } extension R

我已经为Alamofire的可达性实现了一个小包装器。现在我遇到了一个问题,一个子类没有收到通知

正如dfri在注释中添加的那样,这个问题可以描述为:如果子类实例调用的超类方法被放置在扩展中,则它看不到被重写的子类方法

感谢他出色的要点,显示了常见foo/bar的重现性问题:

协议和扩展逻辑:

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 */