Ответы, которые вы ищете, находятся в документации Swift ABI , в частности TypeMetadata.rst и TypeLayout.rst .
Swift использует виртуальные таблицы, называемые таблицами-свидетелями , для обработки соответствий протокола. Каждый тип имеет одну таблицу свидетелей на каждый протокол, которому он соответствует, и таблица свидетелей имеет одну запись на каждую функцию, требуемую этим протоколом.
Когда у нас есть переменная, тип которой является existential (т.е. статически неизвестно), Swift сохраняет эту переменную в структуре времени выполнения, называемой экзистенциальный контейнер . TypeLayout.rst описывает формат экзистенциального контейнера:
Если для протокола или типа композиции протокола нет ограничения класса,Экзистенциальный контейнер должен принимать значение произвольного размера и выравнивания. Для этого используется буфер фиксированного размера , который имеет три указателя по размеру и выровнен по указателю. Это либо непосредственно содержит значение, если его размер и выравнивание меньше или равно буферу фиксированного размера, либо содержит указатель на распределение сторон, принадлежащее экзистенциальному контейнеру. Тип содержащегося значения определяется его записью метаданных типа, и в него включены таблицы-свидетели для всех требуемых соответствий протокола. Компоновка как будто объявлена в следующей структуре C:
struct OpaqueExistentialContainer {
void *fixedSizeBuffer[3];
Metadata *type;
WitnessTable *witnessTables[NUM_WITNESS_TABLES];
};
Массив witnessTables
содержит одну запись для каждого протокола, который наш экзистенциальный статически , как известно, соответствует- поэтому, если наша переменная имеет тип P1 & P2
, экзистенциал будет содержать ровно два указателя таблицы-свидетеля (экзистенциальный контейнер специфичен для его ограничений протокола, поэтому любые дополнительные протоколы, которым соответствует конкретный тип, игнорируются). Пока у нас есть таблица свидетелей, описывающая соответствие типа протоколу, мы можем создать экзистенциальный контейнер, передать его и использовать таблицу свидетелей для вызова методов протокола.
Итак, как нам добавить соответствиек типу через расширение? Ну, на самом деле нам не нужно изменять какие-либо свойства самого типа;нам просто нужно создать новую таблицу свидетелей. Чтобы реализовать динамическое приведение между экзистенциальными типами, нам нужен какой-то способ зарегистрировать соответствие среде выполнения Swift;это можно сделать, поместив запись о соответствии протокола в назначенный раздел двоичного файла, где среда выполнения может его найти:
Запись о соответствии протокола указывает, что данный тип соответствует определенному протоколу. Записи о соответствии протокола передаются в отдельный раздел, который при необходимости сканируется средой выполнения Swift (например, в ответ на запрос swift_conformsToProtocol ()). Каждая запись соответствия протокола содержит:
Дескриптор протокола , описывающий протокол соответствия, представленный в виде (возможно косвенного) 32-битного смещения относительно поля. Младший бит указывает, является ли это косвенным смещением;второй младший бит зарезервирован для будущего использования.
Ссылка на соответствующий тип , представленный как 32-битное смещение относительно поля. Два младших бита указывают, как представлен соответствующий тип:
- 0: прямая ссылка на дескриптор номинального типа.
- 1: косвенная ссылка на дескриптор номинального типа.
- 2: зарезервировано для будущего использования.
- 3: ссылка на указатель на объект класса Objective-C.
The поле таблицы свидетелей , которое обеспечивает доступ к таблице свидетелей, описывающей само соответствие, представленное в виде прямого 32-битного относительного смещения. Два младших бита указывают, как представлена таблица свидетелей:
- 0: Поле таблицы свидетелей является ссылкой на таблицу свидетелей.
- 1: Поле таблицы свидетелей является ссылкой на функцию доступа к таблице свидетелей для безусловного соответствия.
- 2: Поле таблицы свидетелей является ссылкой на функцию доступа к таблице свидетелей для условного соответствия.
- 3: Зарезервировано для будущего использования.
A 32-битовое значение зарезервировано для будущего использования.