首页 > 解决方案 > 为什么 Swift 协议中的变异方法会无限递归,除非只有扩展方法?

问题描述

我在 bugs.swift.org 上的 SR-142 中遇到了以下代码。

如果一个协议有一个正在变异的扩展方法,一个类实例可以毫无问题地调用变异函数。

// 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()一遍又一遍地呼唤自己?

标签: swiftprotocols

解决方案


随着您在第二个示例中的更改,通过m在协议定义中包含 ,指示 Swift 使用动态调度。因此,当您调用 时p.m(),它会动态确定对象是否覆盖了该方法的默认实现。在这个特定的示例中,这会导致该方法递归地调用自身。

但是在第一个例子中,在协议定义中没有方法的情况下,Swift 将使用静态调度,并且因为p是 type P,它会调用 in 中的m实现P


举例来说,考虑方法不是协议定义的一部分(因此不在“协议见证表”中):

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变量是 type P,但它将动态确定底层类型Foo是否覆盖了该方法:

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

有关动态与静态调度的更多信息,请参阅 WWDC 2016 视频了解 Swift 性能


推荐阅读