【问题标题】:Issue with the call hierarchy of a swift protocol extensionswift 协议扩展的调用层次结构问题
【发布时间】:2016-04-26 23:40:23
【问题描述】:

我为 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
    }

}

现在ViewControllerA采用了这个协议并实现了方法:

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")
    }

}

现在 BreachabilityDidChange() 不再被调用。输出只是123

为了检查逻辑,我什至尝试删除 override 关键字:编译器立即抱怨需要它。

所以我可以说调用层次结构是正确的。可见性。

假设:如果被覆盖的方法被放入它自己的扩展中,它就不再可见/不被调用,但编译器仍然需要 override 关键字。

那是我还没有遇到过的事情,或者是我今天在同一个项目上努力工作..

有什么提示吗?

【问题讨论】:

    标签: swift protocols


    【解决方案1】:

    在扩展中覆盖超类方法:仅允许用于 Objective-C 兼容方法

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


    通过 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 属性。

    A 中为reachabilityDidChange(...) 强制执行动态调度

    因此,如果您将dynamic 修饰符添加到超类A 中的方法reachabilityDidChange(...),那么对reachabilityDidChange(...) 的访问将始终使用Objective-C 运行时动态调度,因此可以找到并使用B 扩展类中正确覆盖的 reachabilityDidChange(...) 方法,用于 B 的实例。因此,

    dynamic func reachabilityDidChange(online: Bool) { ... } 
    

    A 将修复上述问题。


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

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

    【讨论】:

    • 很好的答案,非常感谢@dfri!虽然我基本上知道动态与静态调度,但给定的代码在提交之前通过了代码审查:默默地“失败”(虽然由于优化而在逻辑上是正确的)但需要关键字 override 是一件危险的事情:即使我需要使用动态调度,例如领域属性,我忽略了它与 UIViewController 超类子类扩展。看起来 SwiftBond 的作者也遇到了一些调度问题(但不是完全相同的问题):openradar.me/23067007
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多