为什么一个在 Swift 协议中的可变方法会无限递归,除非只是一个扩展方法?

3
我在bugs.swift.org上看到了以下代码。
如果一个协议有一个扩展方法是mutating的,那么一个类实例调用这个mutating函数就没有任何问题。
// protocol definition
protocol P { }

extension P {
    mutating func m() { }
}

// class conforming to P
class C : P {
    // redeclare m() without the mutating qualifier
    func m() {
        // call protocol's default implementation
        var p: P = self 
        p.m()
    }
}

let c = C()
c.m()

如果我对协议声明进行小改动以添加方法:
protocol P {
  mutating func m()  // This is what I added.
}

extension P { 
  mutating func m() { } 
}

class C : P { 
  func m() { 
    var p: P = self 
    p.m() 
  }
}

let c = C() 
c.m()         // This one is calling itself indefinitely; why?

为什么c.m()会一遍又一遍地调用自己?
2个回答

3
在第二个例子中,通过在协议定义中包含m,指示Swift使用动态调度。因此,当您调用p.m()时,它会动态确定对象是否覆盖了该方法的默认实现。在这个特定的例子中,这导致方法递归调用自身。
但在第一个例子中,由于该方法不是协议定义的一部分,Swift将使用静态调度,并且因为p的类型为P,所以它将调用P中的m实现。
例如,考虑方法不是协议定义的一部分的情况(因此不在“协议见证表”中):
protocol P {
    // func method()
}

extension P {
    func method() {
        print("Protocol default implementation")
    }
}

struct Foo: P {
    func method() {
        print(“Foo implementation")
    }
}

由于foo是一个P引用,而method不是P定义的一部分,因此它将method从协议见证表中排除,并使用静态分派。因此,以下内容将打印“Protocol default implementation”:

let foo: P = Foo()
foo.method()              // Protocol default implementation

但是,如果您更改协议以明确包含此方法,同时保持其他所有内容不变,method 将被包含在协议见证表中:

protocol P {
    func method()
}

那么现在以下内容将打印出“Foo implementation”,因为虽然foo变量的类型是P,但它将动态确定底层类型Foo是否已经覆盖了该方法:
let foo: P = Foo()
foo.method()              // Foo implementation

有关动态派发和静态派发的更多信息,请参见WWDC 2016视频了解Swift性能

1
通过在协议中声明m并在您的类中提供实现,它会覆盖默认实现。
但是在第一个示例中,当您将类强制转换为协议时,它将调用协议的默认实现,因为该类的实现是其自己的,不会覆盖任何协议方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接