Сравните some Protocol и any Protocol в Swift: как различаются скрытие конкретного типа и работа с экземпляром протокола?
some Protocol скрывает конкретный тип, но сохраняет его единственность и статическую информацию для компилятора. any Protocol создаёт existential-значение, способное хранить экземпляр любого подходящего типа; конкретный тип при этом стирается, а доступ к нему идёт через требования протокола.
some обычно выбирают для результата функции, когда реализацию нужно скрыть, но сохранить статическую типизацию. any используют, когда значение нужно хранить, передавать независимо от конкретного типа или помещать в гетерогенную коллекцию.
Протоколы Swift решают две разные задачи: описывают общий интерфейс и позволяют скрывать конкретную реализацию. Ранее эти сценарии часто выглядели похоже в исходном коде, хотя имели разные последствия для типовой системы и производительности.
Opaque-типы (some) нужны для публикации абстрактного результата без раскрытия конкретного типа наружу. Existential-типы (any) нужны для хранения значения, чей конкретный тип может быть выбран динамически. Явное написание any помогает отличать стирание типа от сохранения единственного скрытого типа.
Предположим, библиотека возвращает объект, реализующий протокол, но не хочет связывать клиентский код с конкретным классом или структурой. Если выбрать any, клиент получит гибкость, но потеряет часть статической информации о типе; если выбрать some, результат останется конкретным для данной декларации, что ограничивает замену разными реализациями.
Неверный выбор проявляется при попытке собрать гетерогенную коллекцию из результатов или, наоборот, использовать несколько значений как один и тот же конкретный тип. Это влияет на доступные операции, совместимость API и иногда на стоимость хранения значения.
some Protocol означает: «существует один конкретный тип, соответствующий протоколу, но его имя скрыто». Для конкретной декларации этот тип фиксирован: все вызовы функции с таким результатом имеют один и тот же скрытый тип. Поэтому компилятор может сохранять связанные с ним статические гарантии, хотя вызывающий код не видит его имени.
any Protocol означает контейнер протокольного типа. В него можно поместить любой экземпляр, соответствующий протоколу, и значения разных конкретных типов можно хранить вместе. Вызов возможен только через требования протокола; конкретные свойства и методы реализации недоступны без явного приведения типа.
В примере makeDrawable скрывает Circle, но возвращает один фиксированный конкретный тип. Массив many использует any Drawable, поэтому допускает значения разных типов, включая значение с opaque-типом после его помещения в existential-контейнер.
some не означает «любой тип при каждом вызове»: нельзя без дополнительных механизмов возвращать из одной декларации разные несвязанные типы. any не означает автоматически плохую производительность: контейнер может хранить значение непосредственно или использовать дополнительное размещение, а оптимизации зависят от контекста. Однако стирание типа обычно уменьшает доступную компилятору статическую информацию.
Команда проектирует API фабрики виджетов. Вариант с any Widget позволяет вернуть разные реализации в зависимости от конфигурации и хранить их в одном массиве. Минус — клиент видит только требования Widget, а операции, зависящие от конкретного типа, становятся недоступны или требуют приведения.
Вариант с some Widget скрывает внутреннюю реализацию и позволяет библиотеке менять её без изменения публичного имени типа. Минус — одна функция не может таким способом прозрачно выдавать разные конкретные типы в разных ветвях, а результат нельзя напрямую использовать как набор произвольных Widget без преобразования.
Если фабрика всегда возвращает одну реализацию, выбран some Widget: это сохраняет статические гарантии и скрывает деталь реализации. Если фабрика действительно выбирает разные реализации во время выполнения или требуется гетерогенная коллекция, выбран any Widget; потеря части статической информации в этом случае оправдана гибкостью.
1. Можно ли вернуть разные конкретные типы из одной функции с результатом some Protocol?
Нет, обычный opaque-результат одной декларации должен представлять один конкретный тип. Даже если все варианты соответствуют протоколу, разные типы нарушают требование единого скрытого типа. Для динамического выбора используют any Protocol, общий конкретный тип-обёртку или другой дизайн API.
2. Чем any Protocol отличается от обобщённого параметра T: Protocol?
T сохраняет конкретный тип в рамках вызова и позволяет связывать несколько параметров или результат с одним и тем же типом. any Protocol стирает конкретный тип и допускает значение любого соответствующего типа. Поэтому обобщённый параметр предпочтителен, когда важна типовая связь между частями операции, а existential — когда нужна динамическая разнородность.
3. Почему требования протокола иногда нельзя вызвать через any Protocol так же свободно, как через T: Protocol?
Existential-контейнер скрывает конкретный тип, поэтому операции, зависящие от связанных типов, Self или точной типовой идентичности, могут быть недоступны в обобщённом виде. У T: Protocol конкретный тип известен как единый параметр внутри текущего контекста, и такие связи сохраняются. Если операция требует этой информации, следует передать значение в обобщённую функцию или применить type erasure, явно спроектированный для нужного интерфейса.