Программирование
Постепенный перенос проектов и пакетов на Swift 6
С выходом крупного релиза Swift 6 пришло время обновить наш проект и пакеты и устранить все гонки данных.
Apple анонсировала Swift 6 на WWDC 2024 как крупный релиз своего языка программирования. Его цель — создать фантастический опыт разработки, и многие из новейших функций, о которых мы знаем сегодня, являются частью пути к этой крупной версии.
Команда разработчиков Swift ранее поделилась своими приоритетными направлениями на 2023 год и опубликовала подробную информацию о пути к Swift 6. Оба документа описывают видение будущего Swift и помогли нам подготовиться к миграции нашего проекта. В этой статье я расскажу вам, как постепенно перенести проекты и пакеты Xcode на Swift 6.
Чего ожидать от Swift 6?
Все релизы Swift 5.x создают предпосылки для запуска Swift 6 и продвигаются к целям, поставленным перед шестой версией языка. Поначалу эти релизы могут показаться незначительными, но такие примеры, как релиз async/await в Swift 5.5, доказывают обратное. Все они были маленькими шагами на пути к этому крупному шестому релизу.
В этом значительном релизе также появилось несколько новых функций, таких как typed throws, но основное внимание было уделено устранению всех состояний гонок.
Устранение всех data races
Целью Swift 6 всегда было устранение всех гонок данных. Как только вы переведете свои проекты на Swift 6, вы заметите несколько предупреждений, связанных с Sendable и параллелизмом. Эти предупреждения направляют вас на то, чтобы сделать проект потокобезопасным, устранить гонки данных и условия гонки (это не одно и то же).
Некоторые сбои в вашем приложении, скорее всего, связаны с гонками данных, а вы не знаете, как их воспроизвести. Не удивляйтесь, если после успешной миграции они исчезнут.
Дата выхода Swift 6
Swift 6 был запущен на WWDC 2024 11 июня и стал официально доступен вместе с Xcode 16.
Постепенный перенос проектов на Swift 6 в Xcode
Время, необходимое для миграции ваших проектов на Swift 6, зависит от типа и размера вашего проекта. Я рекомендую переходить на него постепенно во всех случаях, чтобы изолировать изменения и дать вам возможность открывать пул реквесты, которые будут достаточно малы для рассмотрения.
При переносе модулей (Swift-пакетов) вы будете следовать тем же шагам, что и при переносе проектов в Xcode, но применять настройки сборки вы будете внутри файла Package.swift. Каждая миграция выполняется в соответствии со следующими шагами:
- Определите изолированную часть вашего проекта. Это будет либо отдельная цель, либо тестовая цель, либо модуль.
- Поочередно включите новые возможности языка Swift 6.
- Повысьте строгую проверку параллелизма с минимальной до целевой и, наконец, до полной.
- После устранения всех предупреждений на каждом шаге вы сможете изменить языковой режим на Swift 6.
Прежде чем погрузиться в детали, вот небольшое руководство к действию, поскольку для более масштабных проектов это может оказаться довольно сложной задачей:
- Не торопитесь
- Не паникуйте
- Вполне нормально, если во время миграции в вашем проекте будут возникать предупреждения.
С учетом этого мы готовы перейти к более подробному описанию.
1. Определение изолированной части проекта
При проведении крупного рефакторинга обычно рекомендуется сосредоточиться на изолированном участке кода. Переход на Swift 6 — это, безусловно, потенциально большой рефакторинг, поэтому важно выбрать изолированный кусок кода. Под этим я подразумеваю код, который может быть скомпилирован изолированно. Примером могут служить цели или отдельные модули.
Если есть возможность, постарайтесь выбрать расширение приложения с меньшим количеством кодовых файлов. Это позволит вам ознакомиться с переносом части кода на Swift 6.
2. Поочередное включение новых возможностей Swift 6
Следующим шагом будет поочередное включение предстоящих языковых функций. Для этого нужно зайти в настройки сборки и найти Upcoming features:
В отфильтрованном списке настроек сборки показаны доступные предстоящие языковые функции и настройка строгой проверки параллелизма. Я рекомендую сосредоточиться на настройках сборки, содержащих переменную $(SWIFT_UPCOMING_FEATURE_6_0)
, поскольку они относятся непосредственно к Swift 6. Эти функции также будут включены автоматически, когда вы измените языковые характеристики проекта на шестую версию.
Скорее всего, вы увидите новые предупреждения после включения одной из грядущих функций. Некоторые из этих предупреждений станут ошибками после обновления версии языка, поэтому постарайтесь исправить как можно больше. Как только это будет сделано, откройте пул реквест с этими изменениями, а затем переходите к следующей функции.
Для пакетов Swift включить предстоящие функции можно следующим образом:
.target( name: "WindowMonitoring", dependencies: [], swiftSettings: [ .enableUpcomingFeature("SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES") ] )
Ключ для каждой новой функции можно найти в быстрой справке Xcode после выбора настроек сборки:
3. Включение строгой проверки параллелизма
Включение предстоящих функций по очереди подготавливает ваш проект к строгой проверке параллелизма. Настройка строгой проверки параллелизма контролирует уровень применения Sendable и проверки изоляции агентов, выполняемой компилятором Swift.
Можно выбрать один из трех уровней:
- Минимальный: Применяет ограничения Sendable только там, где они были явно приняты, и выполняет проверку изоляции агентов везде, где код принял concurrency.
- Целевой: Применение ограничений Sendable и проверка изоляции агентов везде, где код принял параллелизм, включая код, который явно принял Sendable.
- Полный: Применение ограничений Sendable и проверка изоляции агентов во всем проекте или модуле.
Каждый шаг приводит к более строгой проверке и потенциально большему количеству предупреждений. Не стоит слишком торопиться и лучше внедрять каждый уровень по отдельности. После устранения предупреждений на каждом уровне можно открыть рул реквест и перейти на следующий уровень.
Если вы используете пакеты Swift, вы можете изменить уровень строгого параллелизма следующим образом:
.target( name: "CoreExtensions", dependencies: ["Logging"], path: "CoreExtensions/Sources", swiftSettings: [ /// Used to be like this in Xcode 14: SwiftSetting.unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"]), /// Xcode 15 & 16. Remove `=targeted` to use the default `complete`. Potentially isolate to a platform to further reduce scope. .enableExperimentalFeature("StrictConcurrency=targeted", .when(platforms: [.macOS])) ] )
Более подробную информацию вы можете найти в моей статье, посвященной этой настройке сборки. Предупреждения (или ошибки), появляющиеся после включения этой настройки, дают вам представление о том, в каких областях можно улучшить работу. Командам я рекомендую включить этот параметр по умолчанию, чтобы миграция вашей кодовой базы была изящной. Реализации кода, такие как сетевые слои, могут стать отличным началом, поскольку они, вероятно, позволят вам внедрить async/await в большем количестве мест.
4. Смена версии языка на Swift 6
Последний шаг миграции требует изменения версии языка Swift на Swift 6. Зайдите в настройки сборки и найдите Swift Language Version:
После включения вы все еще можете столкнуться с новыми предупреждениями и ошибками, но, скорее всего, вы уже избавились от кучи предупреждений благодаря постепенным шагам миграции.
Для пакетов убедитесь, что там тоже изменилась swift-tools-version:
// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription
Это первая строка в файлах Package.swift, которая определяет минимальную версию Swift, необходимую для сборки пакета.
Чтобы действительно обновить языковую версию пакета, вам нужно принять следующую настройку Swift:
.target( name: "WindowMonitoring", dependencies: [], swiftSettings: [ .swiftLanguageVersion(.v6) ] )
Обратите внимание, что вы можете удалить любые другие настройки Swift для предстоящих функций или строгой проверки параллелизма, поскольку они будут включены по умолчанию после обновления версии языка.
Часто задаваемые вопросы
Наверняка у вас возникнет множество вопросов, когда вы задумаетесь о переносе своего проекта. Вот список часто задаваемых вопросов, который поможет вам.
Могу ли я перейти на Swift 6 только в том случае, если все мои зависимости уже переведены?
Нет, все проекты, пакеты и зависимости могут мигрировать независимо друг от друга. Это также означает, что вы можете перенести свои проекты до того, как это сделают сторонние зависимости.
Что, если зависимость обновляется до Swift 6, а мой проект еще не мигрировал?
Даже в этом случае вы ничего не заметите. Вы сможете перенести свой проект, когда будете готовы, независимо от зависимостей.
Разве Swift 6 — это не все про async/await?
Важно понимать, что речь идет не только об избавлении от замыканий в пользу async/await. Используя concurrency фреймворк , вы позволите компилятору проверить ваш код на безопасность потоков. Предупреждения о строгости параллелизма укажут, какие типы должны стать sendable, предотвращая создание гонок данных и исключений во время выполнения.
Как existentials связаны со Swift 6?
Как описано в моей статье об экзистенциальных any, Swift 6 заставит вас использовать any перед экзистенциалами, чтобы указать их влияние на производительность. Я рекомендую ознакомиться с экзистенциалами и решить, хотите ли вы начать использовать явное указание экзистенциалов уже сегодня, используя предстоящую функцию языка.
Заключение
С выходом крупного релиза Swift 6 пришло время обновить наш проект и пакеты и устранить все гонки данных. Выполняя миграцию постепенно, вы сможете открывать пул реквесты с меньшими изменениями. В конечном итоге вы получите безопасность во время компиляции и более строгую проверку параллелизма, что предотвратит неприятные ошибки и сбои.
Спасибо!