Что такое имплементация по умолчанию (default implementation) в контексте протоколов Swift и когда она применяется?

Ответ

Имплементация по умолчанию — это реализация метода или свойства протокола, предоставляемая в его extension. Она используется, если соответствующий тип, подписанный на протокол, не предоставляет своей собственной реализации.

Когда происходит выбор имплементации? Выбор между реализацией по умолчанию и кастомной реализацией типа происходит на этапе компиляции (статическая диспетчеризация) для методов, объявленных в расширении протокола. Это не то же самое, что динамическая диспетчеризация через таблицу виртуальных функций (vtable) для методов класса.

Пример и важный нюанс:

protocol Renderable {
    func draw()
}

extension Renderable {
    // Имплементация по умолчанию
    func draw() { print("Default drawing") }
}

struct Square: Renderable {
    // У Square НЕТ своей реализации draw()
}

struct Circle: Renderable {
    // У Circle ЕСТЬ своя реализация
    func draw() { print("Drawing a circle") }
}

let shapes: [Renderable] = [Square(), Circle()]

for shape in shapes {
    shape.draw() // Вызовется ТА реализация, которая известна на момент компиляции для каждого типа.
    // Square.draw() -> Default drawing (из extension)
    // Circle.draw() -> Drawing a circle (из структуры)
}

Ключевой вывод: Имплементация по умолчанию — это инструмент для уменьшения дублирования кода и предоставления базовой функциональности, которую тип может переопределить при необходимости.

Ответ 18+ 🔞

А, ну это же протоколы в свифте, да? Слушай, тут есть одна тема, которая мозги выносит новичкам, но на самом деле всё просто, как три копейки.

Вот смотри, есть у тебя протокол — типа контракта, договора: «эй, чувак, если хочешь со мной работать, у тебя должен быть вот такой метод draw()». И всё бы ничего, но писать каждому эту хуйню — заебёшься. Поэтому придумали имплементацию по умолчанию. Это как если бы ты сказал: «ладно, вот тебе готовая реализация в extension, бери и пользуйся, если сам не придумал ничего умнее».

А теперь главный подвох, на котором все обжигаются: Выбор, какую именно реализацию вызывать — твою кастомную или дефолтную — происходит ещё на этапе компиляции. Это не магия рантайма, как с классами. Компилятор смотрит: «ага, Circle написал свой draw() — окей, берём его. Square нихуя не написал — ну тогда берём дефолтный из расширения протокола». И всё, приехали.

Вот, глянь на этот код, тут всё как на ладони:

protocol Renderable {
    func draw()
}

extension Renderable {
    // Это и есть та самая «затычка», если у типа своя голова не варит
    func draw() { print("Default drawing") }
}

struct Square: Renderable {
    // Тишина. Ничего своего. Значит, будет дефолтный draw.
}

struct Circle: Renderable {
    // А вот этот умник решил по-своему
    func draw() { print("Drawing a circle") }
}

let shapes: [Renderable] = [Square(), Circle()]

for shape in shapes {
    shape.draw() // Компилятор уже всё решил за нас!
    // Square -> пойдёт дефолтная печать "Default drawing"
    // Circle -> а этот запорет своё "Drawing a circle"
}

Итог, блядь: Имплементация по умолчанию — это не волшебная палочка, а просто удобная хуйня, чтобы не копипастить один и тот же код в десять мест. Хочешь быть уникальным — переопределяй. Не хочешь — сиди на дефолте, как Square. Всё просто, ёпта. Главное — помни, что решение принимается статически, а не в рантайме, и тогда у тебя не будет неожиданных пиздецов в коде.