问题与swift协议扩展的调用层次结构

时间:2016-04-26 23:40:23

标签: swift protocols

我为Alamofire的可达性实施了一个小包装。现在我遇到了一个子类未接收通知的问题。

正如dfri在评论中添加的那样,问题可以描述为子类实例调用的超类方法如果放在扩展名中则看不到重写的子类方法。

感谢他出色的要点,通常的foo / bar显示可重现的问题:https://gist.github.com/dfrib/01e4bf1020e2e39dbe9cfb14f4165656

协议&扩展逻辑:

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
    }

}

现在,ViewController A 采用此协议并实现方法:

class A: UIViewController, ReachabilityProtocol {

    func reachabilityDidChange(online: Bool) {
        debugPrint("123")
    }

}

在此工作,打印123

ViewController B 是一个子类,并从 A 中覆盖该方法:

class B: A {

    override func reachabilityDidChange(online: Bool) {
        super.reachabilityDidChange(online)
        debugPrint("234")
    }

}

也在工作。在收到通知时立即打印234123

现在是棘手的部分,在构造代码方面,覆盖方法被移入 B 内部的自己的扩展(与类 B 相同的swift文件):

class B: A {
...
}

// MARK: - Reachability Notification
extension B {

    override func reachabilityDidChange(online: Bool) {
        super.reachabilityDidChange(online)
        debugPrint("234")
    }

}

现在 B ' reachabilityDidChange()不再被调用。输出仅为123

要检查逻辑我甚至试图删除override关键字:编译器立即抱怨需要它。

因此我可以说呼叫层次结构是正确的。可见性。

包含:如果将覆盖的方法放入其自己的扩展程序中,则它不再可见/调用,但编译器仍需要override关键字。

我还没有遇到过这样的事情,或者我今天在同一个项目上工作了很久......

任何提示?

1 个答案:

答案 0 :(得分:3)

覆盖扩展中的超类方法:仅允许与Objective-C兼容的方法

首先,我们注意到,如果方法与Objective-C兼容,则只能覆盖子类扩展中的超类方法。对于派生自NSObject的类,对于所有实例方法都是如此(这里为真,因为UIViewController派生自NSObject)。这包括在例如以下Q& A:

通过Objective-C运行时执行动态分派

现在,从Interoperability - Interacting with Objective-C APIs - Requiring Dynamic Dispatch开始,我们注意到以下内容

  

当Objective-C运行时导入Swift API时,没有   保证动态调度属性,方法,下标或   初始化。 Swift编译器仍然可以虚拟化或内联   成员访问以优化代码的性能,绕过   Objective-C运行时。

     

您可以使用dynamic修饰符要求访问成员   通过Objective-C运行时动态调度

此外,我们从The Language Ref. - Declarations - Declaration Modifyers开始阅读

  

dynamic(修饰符)

     

将此修饰符应用于可以表示的类的任何成员   通过Objective-C。使用dynamic标记成员声明时   修饰符,始终使用动态调度对该成员的访问   Objective-C运行时。对该成员的访问永远不会内联或   由编译器虚拟化。

     

因为会调度标有dynamic修饰符的声明   使用Objective-C运行时,它们被隐式标记为objc   属性。

reachabilityDidChange(...)

中执行A的动态调度

因此,如果将dynamic修饰符添加到超类reachabilityDidChange(...)中的方法A,则始终会使用Objective-C运行时动态调度reachabilityDidChange(...)的访问权限,因此,对于reachabilityDidChange(...)的实例,在类B扩展名中查找并使用正确的重写B方法。因此,

dynamic func reachabilityDidChange(online: Bool) { ... } 
A中的

将解决上述问题。

下面是您上述问题的一个更简单的示例,通过obj-c运行时要求在类foo()中的方法A进行动态调度(相当于类中的方法reachabilityDidChange(...))来兑换A)。

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