Сегодня мы поговорим о том, как активировать режим Swift 6 в ваших Xcode-проектах и SwiftPM-модулях, и каким может быть опыт миграции на новую версию языка.

Что такое «режим Swift 6»?

К сожалению, как нам пояснил Дуг Грегор (Doug Gregor) из Swift Language Workgroup, Swift 6 все-таки не успеет увидеть свет в 2023 году. Но знали ли вы, что Apple уже зарелизила некоторые части Swift 6 в версии 5.8? Да, вы не ослышались. Эти части нового Swift поставляются с Xcode 14.3, но они по умолчанию отключены. Они заработают сразу после выхода Swift 6, что может занять еще один год или даже больше.

Эти фичи вносят ряд критических изменения в Swift, в основном из-за переименования общеизвестных API, корректировки их поведения и добавления новых проверок безопасности в компилятор. Все это в какой-то момент будет активировано с целью помочь улучшить нашу кодовую базу. И нам обязательно придется обновлять наши проекты, чтобы они не поломались из-за этих изменений.

Я решил, что было бы неплохо включить все фичи в моих текущих проектах уже сейчас, чтобы увидеть, есть ли в моей кодовой базе какие-либо критические изменения, о которых мне следует позаботиться. И, конечно же, таким образом я бы смог попробовать некоторые новые фичи, такие как BareSlashRegexLiterals, что дает нам возможность краткой инициализации регулярных выражений с помощью литерального синтаксиса  (/.../).

К счастью, в Swift 5.8 есть легкий универсальный способ активировать все эти опции: нам просто нужно передать в Swift флаг -enable-upcoming-feature, прописав его в OTHER_SWIFT_FLAGS настройках сборки нашего проекта в Xcode. А также нам нужно знать, какие именно фичи нам доступны, но я не смог найти хорошего комплексного обзором (по крайней мер пока). Предложение, в рамках которого был добавлен этот унифицированный способ активации, содержит такой список, но он не обновляется более новыми опциями, добавленными позже, как, например, в SE-0384. Так где же мы можем получить достоверный актуальный список всех поддерживаемых опций?

✨ АПДЕЙТ: теперь вы можете найти эту информацию прямо на Swift.org, просто указав в фильтре флаг “upcoming”.

Swift имеет открытый исходный код, поэтому более авторитетный источник, чем репозиторий Swift на GitHub, трудно себе представить! В нем есть файл Features.def, содержащий строки (ссылка на ветку release/5.8) с  UPCOMING_FEATURE, которые включаю соответствующий номер предложения Swift Evolution и версию Swift, которой они будут активны:

UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
UPCOMING_FEATURE(ForwardTrailingClosures, 286, 6)
UPCOMING_FEATURE(BareSlashRegexLiterals, 354, 6)
UPCOMING_FEATURE(ExistentialAny, 335, 6)

Ниже представлено краткое описание того, что эти опции делают:

  • ConciseMagicFile:
    Делает суть директивы #file ближе к #fileID нежели #filePath.

  • ForwardTrailingClosures:
    Меняет правило сканирования замыканий с конца на правило сканирования с начала.

  • ExistentialAny:
    Делает использование ключевого слова any для экзистенциальных типов обязательным.

  • BareSlashRegexLiterals:
    Добавляет литеральный синтаксис регулярных выражений (/.../).

В этом списке почему-то не хватает еще двух опций (я пытаюсь выяснить почему):

  • StrictConcurrency:
    Добавляет полную проверку параллельного кода. 

  • Implicit Open Existentials:
    Добавляет выполнение неявного открытия для ряда дополнительных сценариев.

В более поздних версиях мы увидим еще больше опций (например, ImportObjcForwardDeclarations).

Ко всему вышеперечисленному я также решил добавить -warn-concurrency (на самом деле это должно быть приблизительно то же, что и StrictConcurrency, если эта опция уже полностью реализована) и -enable-actor-data-race-checks, чтобы протестировать дополненную поддержку параллелизма на своем коде.

Как мой проект пережил миграцию

Если вам тоже интересно попробовать включить в своем проекте все опции, доступные в версии 5.8, то вы можете просто скопировать (C) следующий кусок текста, затем перейдите на вкладку "Build Settings" вашего проекта Xcode, найдите "Other Swift Flags", выберите этот параметр в редакторе и вставьте (V) его туда:

//:configuration = Debug
OTHER_SWIFT_FLAGS = -enable-upcoming-feature BareSlashRegexLiterals -enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ExistentialAny -enable-upcoming-feature ForwardTrailingClosures -enable-upcoming-feature ImplicitOpenExistentials -enable-upcoming-feature StrictConcurrency -warn-concurrency -enable-actor-data-race-checks

//:configuration = Release
OTHER_SWIFT_FLAGS = -enable-upcoming-feature BareSlashRegexLiterals -enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ExistentialAny -enable-upcoming-feature ForwardTrailingClosures -enable-upcoming-feature ImplicitOpenExistentials -enable-upcoming-feature StrictConcurrency -warn-concurrency -enable-actor-data-race-checks

//:completeSettings = some
OTHER_SWIFT_FLAGS

Если вы используете модульное приложение на SwiftPM, как и я, или если вы работаете над пакетом для Swift, вам нужно будет дополнительно передавать массив .enableUpcomingFeature для каждого таргета в swiftSettings:

let swiftSettings: [SwiftSetting] = [
  .enableUpcomingFeature("BareSlashRegexLiterals"),
  .enableUpcomingFeature("ConciseMagicFile"),
  .enableUpcomingFeature("ExistentialAny"),
  .enableUpcomingFeature("ForwardTrailingClosures"),
  .enableUpcomingFeature("ImplicitOpenExistentials"),
  .enableUpcomingFeature("StrictConcurrency"),
  .unsafeFlags(["-warn-concurrency", "-enable-actor-data-race-checks"]),
]

let package = Package(
  // ...
  targets: [
     // ...
     .target(
        name: "MyTarget",
        dependencies: [/* ... */],
        swiftSettings: swiftSettings
     ),
     // ...
  ]

Не забудьте обновить версию инструментов в верхней части файла до 5.8:

// swift-tools-version:5.8

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

После их активации и запуска сборки я столкнулся с четырьмя видами проблем:

1. Мне пришлось добавить в нескольких местах ключевое слово any — Xcode помог мне с этим:

2. Почему-то я получил много ошибок для вью с модификатором .sheet. Это были пары ошибок "Generic parameter 'Content' could not be inferred" («Не возможно вывести тип обобщенного параметра 'Content'») и "Missing argument for parameter 'content' in call" («В вызове недостает аргумента для параметра 'content'»). Однако эти сообщения оказались не очень информативными, поэтому я сначала попытался заменить содержимое .sheet на простенький Text, но это не помогло. Поэтому, я стал пробовать отключать новые опции одну за другой, и ошибки исчезли, когда я выключил ForwardTrailingClosures. По итогам я так и оставил ее выключенной. Я надеюсь, что в будущих версиях Swift появятся более информативные сообщения об ошибках, которые помогут разобраться с этим изменением. А пока спешить некуда. Я могу повторить попытку на Swift 5.9. Сейчас же у меня не было времени разбираться с этим.

3. Мне пришлось пометить некоторые из моих функций, возвращающих ТСА WithViewStore, атрибутом @MainActor, но опять же, Xcode помог мне с этим.

4. Во многих местах всплывали  предупреждения “Non-sendable type ‘…’ passed in call to main actor-isolated function cannot cross actor boundary” («тип ‘…’, не являющийся sendable, переданный в вызове основной функции, изолированной в глобальным актором, не может выходить за границу актора»). Поэтому мне пришлось сделать эти типы соответствующими протоколу Sendable (узнать о Sendable больше можно здесь).

Все остальные ~ 35 тысяч строк кода Swift моего приложения. RemafoX, казалось, собирались без каких-либо проблем. Весь процесс миграции занял не более 3 часов моего времени.

На данном этапе мой проект почти готов к новому Swift ????, и теперь я могу использовать новую литеральную форму Regex (let regex = /.*@.*/). Кроме того, теперь я не смогу написать новый код, который мне впоследствии нужно будет переписывать под Swift 6, потому что я сразу же получу ошибки. ????

А что насчет ваших проектов? Какие будущие фич вы ждете больше всего?

Перевод статьи подготовлен в преддверии старта курса iOS Developer. Professional. Также приглашаю на бесплатный вебинар в рамках которого поговорим о технологии Deep links на iOS, произведем настройку сервера, клиентского приложения для работы с технологией. Напишем простой пример, как открыть определенный экран с помощью Deep links на примере SwiftUI - приложения.

Комментарии (0)