Pull to refresh

Что нового в Swift 6?

Level of difficultyMedium
Reading time7 min
Views4.8K

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 для создания высокопроизводительных приложений. 

Полагаю что это только начало, начало огромного будущего нами всеми любимого языка.

Tags:
Hubs:
Total votes 3: ↑3 and ↓0+5
Comments0

Articles