调用Swift协议扩展方法而不是在子类中实现的方法

时间:2017-06-22 15:08:28

标签: swift protocols

我遇到了下面的代码(Swift 3.1)中解释的问题:

protocol MyProtocol {
    func methodA()
    func methodB()
}

extension MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocol {

}

class SubClass: BaseClass {
    func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocol {
    func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// Default methodA
// JustClass methodA

所以我希望在object1.methodB()调用后打印“SubClass methodA”文本。但由于某种原因,调用了协议扩展的methodA()默认实现。但是,object2.methodB()调用按预期工作。

是协议方法调度中的另一个Swift错误还是我遗漏了某些内容并且代码正常工作?

3 个答案:

答案 0 :(得分:28)

这就是协议当前如何调度方法。

协议见证表(请参阅this WWDC talk了解更多信息)用于在协议类型实例上调用时动态调度协议要求的实现。它实际上只是函数实现的列表,用于为给定的符合类型调用协议的每个需求。

表明其与协议一致性的每种类型都有自己的协议见证表。你会注意到我说'#34;陈述其符合性#34;而不只是"符合"。 BaseClass获取自己的协议见证表以符合MyProtocol。但是SubClass

}

}如果您将MyProtocol向下移动到BaseClass的定义,它将拥有自己的PWT。

所以我们在这里要考虑的是: MyProtocol的PWT是什么样的。好吧,它没有提供任何协议要求SubClassBaseClass的实现 - 所以它依赖于协议扩展中的实现。这意味着符合methodA()的{​​{1}}的PWT只包含对扩展方法的映射。

因此,当调用扩展methodB()方法并调用BaseClass时,它会通过PWT动态调度该调用(因为它在协议上被调用 - 键入的实例;即MyProtocol)。因此,当methodB()个实例发生这种情况时,我们会通过methodA()的PWT。因此,我们最终会调用self的扩展实现,而不管SubClass是否提供了它的实现。

现在让我们考虑BaseClass的PWT。它提供了methodA()的实现,因此其与SubClass一致的PWT具有 实现作为JustClass的映射,以及methodA()的扩展实现{1}}。因此,当MyProtocol通过其PWT动态调度时,我们最终会进入实现。

正如我所说in this Q&A,子类的这种行为没有为他们的超类符合的协议获得他们自己的PWT确实有点令人惊讶,并且一直是filed as a bug。正如Swift团队成员Jordan Rose在错误报告的评论中所说,其背后的原因是

  

[...]子类无法提供满足一致性的新成员。这很重要,因为协议可以添加到一个模块中的基类和另一个模块中创建的子类。

因此,如果这是行为,那么已经编译的子类将缺少在另一个模块中添加的超类一致性的任何PWT,这将是有问题的。

正如其他人已经说过的,在这种情况下,一个解决方案是让methodA()提供自己的methodB()实现。此方法现在将在methodA()的PWT中,而不是扩展方法。

虽然当然,因为我们在这里处理,但它不仅仅是BaseClass的方法的实施,而且#39>&#39> ; s列出 - 而不是thunk,然后动态调度通过班级' vtable(类实现多态的机制)。因此,对于methodA()实例,我们最终会调用其覆盖BaseClass

答案 1 :(得分:0)

我认为子类方法A不是多态的,因为你不能把override关键字放在它上面,因为类不知道该方法是在协议的扩展中实现的,因此不允许你覆盖它。扩展方法可能是在运行时踩到你的实现,就像2个精确的类别方法在目标C中使用未定义的行为相互胜过。你可以通过在模型中添加另一个层并在类中实现方法来修复此行为而不是协议扩展,从而获得多态行为。缺点是您不能在此层中保留未实现的方法,因为没有对抽象类的本机支持(这实际上是您尝试使用协议扩展)

protocol MyProtocol {
    func methodA()
    func methodB()
}

class MyProtocolClass: MyProtocol {
    func methodA() {
        print("Default methodA")
    }

    func methodB() {
        methodA()
    }
}

// Test 1
class BaseClass: MyProtocolClass {

}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}


let object1 = SubClass()
object1.methodB()
//

// Test 2
class JustClass: MyProtocolClass {
    override func methodA() {
        print("JustClass methodA")
    }
}

let object2 = JustClass()
object2.methodB()
//
// Output
// SubClass methodA
// JustClass methodA

此处还有相关答案:Swift Protocol Extensions overriding

答案 2 :(得分:0)

在您的代码中,

let object1 = SubClass()
object1.methodB()

您从SubClass的实例调用了methodB,但SubClass没有任何名为methodB的方法。然而,它的超级类BaseClass符合MyProtocol,它有methodB方法B.

因此,它会从methodB调用MyProtocal。因此,它将执行methodA中的extesion MyProtocol

要达到您的预期,您需要在methodA中实施BaseClass并在SubClass中覆盖它,如下面的代码

class BaseClass: MyProtocol {
    func methodA() {
        print("BaseClass methodA")
    }
}

class SubClass: BaseClass {
    override func methodA() {
        print("SubClass methodA")
    }
}

现在,输出将变为

//Output
//SubClass methodA
//JustClass methodA

虽然该方法可以达到您的预期,但我不确定是否建议使用这种代码结构。