Я наткнулся на следующий код в SR-142 на bugs.swift.org.
Если в протоколе есть изменяющий метод расширения, экземпляр класса может без проблем вызвать изменяющую функцию.
// 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() продолжает вызывать себя снова и снова?





Объявляя m в протоколе и предоставляя реализацию в вашем классе, он перезаписывает реализацию по умолчанию.
Но в первом примере, когда вы используете свой класс как протокол, он вызовет реализацию протокола по умолчанию, потому что реализация класса является его собственной и не перезаписывает какой-либо метод протокола.
С вашим изменением во втором примере, включая m в определение протокола, это указывает Swift на использование динамической диспетчеризации. Поэтому, когда вы вызываете p.m(), он динамически определяет, переопределил ли объект реализацию метода по умолчанию. В данном конкретном примере это приводит к тому, что метод рекурсивно вызывает сам себя.
Но в первом примере, при отсутствии метода, являющегося частью определения протокола, Swift будет использовать статическую диспетчеризацию, и поскольку p имеет тип P, он вызовет реализацию 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 из таблицы-свидетеля протокола и использует статическую диспетчеризацию. В результате будет напечатано «Реализация протокола по умолчанию»:
let foo: P = Foo()
foo.method() // Protocol default implementation
Но если вы измените протокол, чтобы явно включить этот метод, оставив все остальное без изменений, method будет включен в таблицу-свидетель протокола:
protocol P {
func method()
}
Затем следующее теперь напечатает «реализация Foo», потому что, хотя переменная foo имеет тип P, она будет динамически определять, переопределил ли базовый тип Foo этот метод:
let foo: P = Foo()
foo.method() // Foo implementation
Дополнительные сведения о динамической и статической диспетчеризации см. в видеоролике WWDC 2016 Понимание производительности Swift.