ПрограммированиеiOS разработчик, Middle/Senior

Что такое opaque types (some) в Swift, зачем они нужны и когда стоит их применять вместо protocol types?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса: Opaque types (some) появились с Swift 5.1 и открыли новый способ абстракции возвращаемого значения функции или свойства, когда тип известен компилятору, но скрыт от пользователя. Это альтернатива protocol existentials (any Protocol), но с жёсткой привязкой к конкретному типу внутри функции.

Проблема: Когда функция возвращает протокол с associatedtype (например, Sequence), нельзя напрямую написать:

func makeNumberSequence() -> Sequence { ... } // ошибка

Protocol existentials позволяют возвращать любую реализацию, но не гарантируют один и тот же тип при каждом вызове:

func foo() -> any View { ... }

Это приводит к непредсказуемости и слабому type safety.

Решение: Использовать opaque result type:

func makeNumbers() -> some Sequence { [1, 2, 3] }

Теперь компилятор точно знает фактический возвращаемый тип, но он скрыт снаружи. Это даёт оптимизации производительности, safety, позволяет использовать SwiftUI DSL, облегчает обмен типами между модулями.

Ключевые особенности:

  • Конкретный тип внутри opaque типа всегда один
  • Не поддерживает associatedtype в месте использования, а не в определении
  • Используется для построения декларативных API (например SwiftUI)

Вопросы с подвохом.

Могут ли opaque types использоваться для хранения значения (как свойства класса)?

Нет. Opaque types применяются только для возвращаемых значений функции или computed properties. Для хранения значения или массива значений используют existentials (any Protocol).

Могут ли разные ветви одной функции возвращать разные типы с some?

Нет. Компилятор требует, чтобы обе (или все) ветви возвращали один и тот же конкретный тип, иначе будет ошибка:

func foo(flag: Bool) -> some Sequence { if flag { return [1, 2, 3] } else { return ["a", "b", "c"] // ошибка } }

Можно ли использовать some для идентификации возвращаемого типа между несколькими функциями?

Нет. Каждая функция с some возвращает свой уникальный скрытый тип, даже если фактически это один и тот же массив. Использовать результат одной функции как параметр в другую нельзя, если обе используют some с разными протоколами или разными скрытыми типами.

Типовые ошибки и анти-паттерны

  • Пытаются вернуть разные типы через some (ошибка компиляции)
  • Используют some там, где требуются protocol existentials (например, для хранения состояний)
  • Упускают оптимизации, продолжая использовать old-style protocol types

Пример из жизни

Негативный кейс

В проекте всё возвращается через any Protocol, коллекции теряют типизацию, появляются баги при downcast-e, тормозит compile-time оптимизация.

Плюсы:

  • Гибкая архитектура с возможностью комбинировать разные типы

Минусы:

  • Потеря type safety, снижение эффективности, проблемы с кастами

Позитивный кейс

В SwiftUI-дизайне компоненты возвращают some View, каждый модуль чётко определяет внутренний тип. Размер бандла сокращается, сборка ускоряется.

Плюсы:

  • Улучшение производительности, рост type safety

Минусы:

  • Некоторая потеря гибкости при необходимости динамического хранения