Swift 6 — это важное обновление языка программирования Swift, разработанного Apple. Данная версия включает множество нововведений и улучшений, особенно в области параллелизма. В этом документе мы рассмотрим основные изменения и их влияние на разработчиков, опираясь на официальную документацию Apple.
Пожалуйста, обратите внимание, что все упомянутые изменения являются актуальными на момент выхода Swift 6, и в будущем могут появиться новые обновления и дополнения. Рассмотрим основные нововведения Swift 6, начиная с изменений в доменах изоляции и параллелизма.
Полный параллелизм включен по умолчанию
Swift 6 включает в себя множество обновлений, касающихся параллелизма, и команда разработчиков может гордиться своими достижениями. Самое значительное изменение заключается в том, что полная проверка параллельности теперь включена по умолчанию. Это изменение может потребовать некоторых корректировок в вашем коде, что неудивительно, так как в предыдущих версиях эта функция была опциональной, чтобы дать разработчикам время адаптироваться к изменениям.
Swift 6 улучшает проверку параллелизма, устраняя множество ложных предупреждений о гонках данных, которые присутствовали в версии 5.10. Внесены также ряд целевых изменений, которые делают параллелизм легче для освоения.
Одним из крупнейших нововведений является SE-0414, который вводит домены изоляции, позволяющие компилятору убедительно доказать, что различные части вашего кода могут выполняться параллельно.
В основе этого изменения лежит существующая концепция отправляемости (sendability). Тип Sendable — это такой тип, который можно безопасно передавать в параллельной среде. Это могут быть типы значений, такие как структуры, финальные классы с константными свойствами, акторы, которые автоматически защищают свое изменяемое состояние, и многое другое.
Ранее компилятор Swift был очень строг: если у вас было значение, не поддающееся отправке (non-sendable), и вы пытались передать его другому актору, вы получали предупреждение о проверке параллельности. Например, хотя тела представлений SwiftUI выполняются на основном акторе, сами представления SwiftUI этого не делают, что может легко вызвать ложные предупреждения компилятора.
Рассмотрим следующий код:
class User {
var name = "Anonymous"
}
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.task {
let user = User()
await loadData(for: user)
}
}
func loadData(for user: User) async {
print("Loading data for \(user.name)…")
}
}
До Swift 6 вызов функции loadData() вызвал бы предупреждение: "передача аргумента типа 'User' за пределы изолированного контекста основного актора может вызвать гонки данных."
В Swift 6 это предупреждение исчезает: Swift теперь определяет, что код не представляет проблемы, так как user не используется одновременно из нескольких мест, и не выдаст предупреждение. Компилятор анализирует поток программы и определяет, что это безопасно.
Это изменение означает, что объекты, пригодные для отправки, теперь либо соответствуют протоколу Sendable, либо не нуждаются в этом, потому что компилятор может доказать, что они используются безопасно — это значительное улучшение параллелизма для разработчиков, возможно благодаря передовым разработкам компилятора.
Также внесены множество других, более мелких улучшений, включая:
SE-430 добавляет новое ключевое слово sending, когда нужно отправить значения между доменами изоляции.
SE-0423 улучшает поддержку параллельности при работе с фреймворками Objective-C.
SE-0420 позволяет создавать асинхронные функции, изолированные тем же актором, что и их вызывающий код.
Устаревшие функции
Некоторые изменения были доступны в предыдущих версиях Swift, но скрыты за флажками функций. Например, SE-0401 удаляет функцию, введенную в Swift 5.5: вывод акторов для оберток свойств.
Ранее любой структурный или классовый тип, использующий обертку свойства с @MainActor для его обернутого значения, автоматически становился @MainActor. Это позволило @StateObject и @ObservedObject передавать "main-actor-ness" на представления SwiftUI, которые их используют.
@MainActor
class ViewModel: ObservableObject {
func authenticate() {
print("Authenticating…")
}
}
@MainActor
struct LogInView: View {
@StateObject private var model = ViewModel()
var body: some View {
Button("Hello, world", action: startAuthentication)
}
func startAuthentication() {
model.authenticate()
}
}
Ранее @MainActor присваивался бы всему представлению из-за его свойства @StateObject.
Строгий параллелизм для глобальных переменных
Еще одно старое изменение, теперь включенное в Swift 6, — SE-0412, требующее, чтобы глобальные переменные были безопасны в параллельных средах. Это касается как глобальных переменных в проекте, так и статических переменных в типах.
Примеры:
var gigawatts = 1.21
struct House {
static var motto = "Winter is coming"
}
Эти данные могут быть доступны в любое время, что делает их небезопасными. Чтобы решить эту проблему, нужно либо преобразовать переменную в sendable-константу, либо ограничить ее глобальным актором, например, @MainActor, либо, если нет других вариантов, пометить её как nonisolated.
struct XWing {
@MainActor
static var sFoilsAttackPosition = true
}
struct WarpDrive {
static let maximumSpeed = 9.975
}
@MainActor
var idNumber = 24601
// Не рекомендуется, если вы не уверены, что это безопасно
nonisolated(unsafe) var britishCandy = ["Kit Kat", "Mars Bar", "Skittles", "Starburst", "Twix"]
Изменения в значениях по умолчанию для функций
Другое изменение, которое теперь включено, — SE-0411, которое изменяет значения по умолчанию для функций так, чтобы они имели ту же изоляцию, что и сама функция.
@MainActor
class Logger {}
@MainActor
class DataController {
init(logger: Logger = Logger()) {}
}
Поскольку и DataController, и Logger ограничены основным актором, Swift теперь считает создание Logger() также ограниченным основным актором, что имеет смысл.
Новый метод count(where:)
SE-0220 ввел новый метод count(where:), который выполняет эквивалент filter() и count в одном проходе. Это экономит создание нового массива, который сразу же отбрасывается, и предоставляет ясное и лаконичное решение распространенной проблемы.
Пример:
let scores = [100, 80, 85]
let passCount = scores.count { $0 >= 85 }
Этот метод доступен для всех типов, которые соответствуют Sequence, так что вы можете использовать его для множеств и словарей.
Типизированные выбросы ошибок (Typed Throws)
SE-0413 ввел возможность указать, какие именно типы ошибок может выбросить функция, известную как "typed throws". Это решает неудобство с ошибками в Swift: раньше требовалось общее выражение catch, даже если вы специально обрабатывали все возможные ошибки.
Пример:
enum CopierError: Error {
case outOfPaper
}
struct Photocopier {
var pagesRemaining: Int
mutating func copy(count: Int) throws(CopierError) {
guard count <= pagesRemaining else {
throw CopierError.outOfPaper
}
pagesRemaining -= count
}
}
do {
var copier = Photocopier(pagesRemaining: 100)
try copier.copy(count: 10)
} catch CopierError.outOfPaper {
print("Please refill the paper")
}
Итерация пакета (Pack Iteration)
SE-0408 вводит итерацию по пакетам, что добавляет возможность обхода параметров пакета, введенных в Swift 5.9. Это позволяет, например, сравнивать кортежи любой арности всего в нескольких строках кода:
func == <each Element: Equatable>(lhs: (repeat each Element), rhs: (repeat each Element)) -> Bool {
for (left, right) in repeat (each lhs, each rhs) {
guard left == right else { return false }
}
return true
}
Операции с элементами коллекций
SE-0270 вводит различные новые методы для работы с коллекциями, такие как перемещение или удаление нескольких элементов, которые не являются смежными. Это изменение поддерживается новым типом RangeSet.
Пример:
struct ExamResult {
var student: String
var score: Int
}
let results = [
ExamResult(student: "Eric Effiong", score: 95),
ExamResult(student: "Maeve Wiley", score: 70),
ExamResult(student: "Otis Milburn", score: 100)
]
let topResults = results.indices { student in
student.score >= 85
}
for result in results[topResults] {
print("\(result.student) scored \(result.score)%")
}
Модификаторы уровня доступа для объявлений импорта
SE-0409 добавляет возможность отмечать объявления импорта модификаторами уровня доступа, такими как private import SomeLibrary. Это помогает разработчикам библиотек избегать случайного утечки собственных зависимостей.
Пример:
// Внутренняя библиотека
public struct BankTransaction {
// код здесь
}
// Основная библиотека
public func sendMoney(from: Int, to: Int) -> BankTransaction {
// обработка перевода денег
return BankTransaction()
}
// Основное приложение
import BankingLibrary
sendMoney(from: 123, to: 456)
С Swift 6 можно использовать internal import Transactions в основной библиотеке, чтобы ограничить видимость.
Улучшения для noncopyable типов
Несколько улучшений для noncopyable типов, введенных в Swift 5.9. Noncopyable типы позволяют создавать типы с уникальным владением, которые можно передавать, используя заимствование или потребление.
Пример:
struct Message: ~Copyable {
var agent: String
private var message: String
init(agent: String, message: String) {
self.agent = agent
self.message = message
}
consuming func read() {
print("\(agent): \(message)")
}
}
func createMessage() {
let message = Message(agent: "Ethan Hunt", message: "You need to abseil down a skyscraper for some reason.")
message.read()
}
createMessage()
В Swift 6, все структуры, классы, перечисления, параметры обобщений и протоколы автоматически соответствуют новому протоколу Copyable, если явно не отказаться от него, используя ~Copyable.
Типы 128-битных целых чисел
SE-0425 вводит Int128 и UInt128. Эти типы работают так же, как и другие целочисленные типы в Swift.
Пример:
let enoughForAnybody: Int128 = 170_141_183_460_469_231_731_687_303_715_884_105_727
Протокол BitwiseCopyable
SE-0426 вводит новый протокол BitwiseCopyable, который позволяет компилятору создавать более оптимизированный код для соответствующих типов.
Пример:
@frozen
public enum CommandLine: ~BitwiseCopyable {
}
Эти улучшения помогают делать noncopyable типы более естественными в использовании и повышают производительность кода.
Swift 6 приносит значительные улучшения, особенно в доменах параллелизма, делая ее более доступной и понятной для разработчиков. Новые возможности, такие как полная проверка параллелизма по умолчанию, домены изоляции и усовершенствованное понятие sendability, упрощают написание безопасного и эффективного кода. Большая часть изменений направлена на адаптацию разработчиков к новым требованиям и использованию мощных функций языка Swift для создания высокопроизводительных приложений.
Полагаю что это только начало, начало огромного будущего нами всеми любимого языка.