Это минорное обновление языка перед мажорным переходом на Swift 6, которое сосредоточено в основном на устранение проблем с data race при компиляции.
Проблемы гонок данных теперь четко диагностируются
Конкурентность в Swift была введена еще в версии Swift 5.5, но её принятие оказалось немного сложным как в нативных фреймворках от Apple, так и в наших собственных проектах. Однако с выпуском Swift 5.10 команда сделала смелое заявление: “Swift 5.10 закрывает все известные уязвимости в статической безопасности от гонок данных при полной строгой проверке конкурентности.”
Проверка конкурентности позволяет компилятору удостовериться, что мы безопасно используем конкурентный код – что мы случайно не делимся изменяемым состоянием, которое может вызвать состояние гонки. Конечно, ключевое слово здесь – “известные”: все известные проблемы были решены.
Работа Apple в этой области не только инновационная, но и чрезвычайно сложная: как и вывод типов, который требует от компилятора Swift анализа использования различных частей нашего кода, проверка конкурентности требует выполнения серии алгоритмов, чтобы окончательно убедиться, что наш код безопасен для параллельного выполнения.
Давайте взглянем на пример:
import SwiftUI
struct ContentView: View {
var body: some View {
Button("Tap Me", action: doWork)
}
private func doWork() {
print("Hello")
}
}
Этот блок кода в Swift 5.9 выдает довольно странный ворнинг: "Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor MainActor
".
Проблема заключалась в том, что в SwiftUI представление Button не использует @MainActor
для своих действий, из-за чего Swift выдавал предупреждение о вызове метода главного актора из места, не изолированного для главного актора. В Swift 5.10 это предупреждение убрали благодаря улучшениям в проверке конкурентности: теперь компилятор понимает, что действие кнопки находится внутри свойства body, которое изолировано для главного актора, и поэтому безопасно.
Разрешение вложенных протоколов в необобщённых (Non-Generic) контекстах
Пропозал SE-0404 позволяет создавать вложенные протоколы. Основные изменения включают возможность объявлять протоколы внутри других протоколов, структур, перечислений и классов. Это улучшает организацию кода, делая его более интуитивно понятным и удобочитаемым, особенно в крупных проектах. Вложенные протоколы позволяют логически группировать связанные протоколы с типами, которые их используют, что способствует лучшему управлению и поддержке кода.
Это особенно полезно, когда протоколам дают общие названия. Например, слово “transaction” может использоваться для обозначения анимационной транзакции, банковской транзакции и транзакции базы данных в одном и том же приложении.
Один из способов решить эту проблему — использовать составные названия, добавляя больше слов к именам протоколов для уточнения их значения, например:
protocol AnimationTransaction {
}
protocol BankTransaction {
}
protocol DatabaseTransaction {
}
Ещё одна распространённая проблема возникает, когда существует несколько похожих протоколов. Например, в SwiftUI есть протоколы для ButtonStyle
, LabelStyle
, ListStyle
и других, все они охватывают идею, что представление можно стилизовать по-разному.
Обе эти проблемы можно решить с помощью этого изменения. В случае транзакций мы могли бы вложить каждый тип транзакции внутрь того типа, с которым он работает:
struct Animation {
protocol Transaction {
}
}
struct Bank {
protocol Transaction {
}
}
struct Database {
protocol Transaction {
}
}
Когда эти протоколы используются извне, они будут записаны как Animation.Transaction
, Bank.Transaction
и Database.Transaction
, но внутри своих соответствующих структур они могут просто называться Transaction
.
Теоретически, SwiftUI также мог бы перейти на Button.Style
, List.Style
и так далее, но на данный момент это кажется слишком большим изменением.
Deprecate @UIApplicationMain и @NSApplicationMain
Пропозал SE-0383 предлагает отмену атрибутов @UIAplicationMain
и `@NSAplicationMain`
в пользу использования атрибута @main
. Эти атрибуты предоставляли точку входа для приложений iOS и macOS, но теперь они создают дублирование и путаницу, так как @main
уже выполняет их функции. В Swift 6 использование `@UIAplicationMain`
и @NSAplicationMain
будет выдавать ошибку. Цель — упростить и унифицировать способ задания точки входа для приложений, улучшив читаемость и поддержку кода.
Было:
import SwiftUI
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// your code here
}
Стало:
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// your code here
}
В SwiftUI без изменений:
@main
struct SandboxApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Переосмысленная инициализация и деинициализация акторов
Предложение SE-0327 касается инициализаторов акторов в Swift. Оно описывает, как инициализация и деинициализация акторов должны работать для обеспечения потокобезопасности и предотвращения гонок данных. Основные моменты включают:
Обозначение синхронных и асинхронных инициализаторов.
Уточнение правил использования
self
в инициализаторах.Обработка изоляции данных инициализируемых свойств.
Введение анализа потоков для предотвращения гонок данных.
Это предложение включает несколько небольших, но конкретных изменений для акторов. Например, Swift теперь автоматически переключает акторов с асинхронным инициализатором на исполнитель акторов, когда все их свойства инициализированы.
На практике это означает, что два вызова print()
, показанные ниже, могут выполняться на совершенно разных потоках:
actor Actor {
var name: String
init(name: String) async {
print(name)
self.name = name
print(name)
}
}
let actor = await Actor(name: "Margot")
Это означает, что код имеет потенциальную приостановку выполнения сразу после установки свойства name.
Переход на Swift 6
Swift 6 вводит огромное количество изменений в язык, включая активацию функционала, который был реализован в более ранних версиях Swift, но по умолчанию был отключен. Такой функционал будет рассмотрен в отдельной статье “Что нового в Swift 6”.
Когда весь новый функционал будет полностью включен в Swift 6, это вызовет различные ошибки компиляции во многих существующих проектах. Поэтому все новые фичи рекомендуется включать по одной, оставаясь как можно дольше на последних версиях Swift 5. Это позволит вам постепенно перейти на Swift 6, в максимально щадящем режиме, без серьёзных сбоев и падений.
Если возникнет желание включить полную проверку конкурентности в Swift 5.10 перед выходом на Swift 6, то не пугайтесь, когда увидите множество ворнингов и ошибок компиляции. Swift 6 добавляет много улучшений для работы с конкурентностью, которые помогут решить эти проблемы. Кроме того Apple скорее всего обновит свои API, чтобы упростить переход.