Асинхронность — не враг, если знаешь, как с ней работать ?.

Асинхронный код — это двигатель современных приложений. 

Он даёт возможность загружать данные с сервера, обновлять интерфейс и выполнять сложные вычисления в фоне, оставляя пользователя в полной уверенности, что всё работает плавно. Звучит здорово, правда? 

Вот только раньше это всё превращало жизнь iOS разработчика в ад: громоздкие замыкания, GCD с его бесконечными очередями и "пирамида смерти" из вложенных вызовов. ?

Но теперь у нас есть Swift Concurrency. Это как освежающий глоток воды после долгого забега. Код становится читаемым, понятным и безопасным. Если вы хотите понять, как этим пользоваться, читайте дальше — объясню всё максимально просто и доступно.

? Замыкания? Никогда больше!

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

fetchData { data in
    processData(data) { result in
        updateUI(result)
    }
}

Читается сложно, выглядит ещё хуже. Это как будто кто-то решил собрать все ошибки начинающего разработчика в одну функцию. Чем больше задач — тем глубже вложенность. Хотите добавить ещё обработку ошибок? Удачи вам в этом лабиринте.

Добро пожаловать в мир async/await ?

let data = await fetchData()
let result = await processData(data)
await updateUI(result)

Теперь ваш код выглядит как простой последовательный процесс, но под капотом остаётся асинхронным. Это не только упрощает чтение, но и делает разработку более комфортной.

Async/await — это ваш ключ к понятному и мощному асинхронному программированию. ✨

? Основные концепции Swift Concurrency: кто и за что отвечает

Swift Concurrency построен на трёх ключевых инструментах. Давайте разберём, что они делают:

Инструмент

Что это?

Когда использовать

Task

Асинхронные задачи

Для выполнения операций в фоне

Actor

Объект для потокобезопасного управления данными

Когда данные используются в нескольких потоках

Structured Concurrency

Организация задач в дерево

Для одновременного выполнения нескольких операций

?️ Task: ваш фрилансер для фоновых задач

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

Task — это ваш фрилансер, который берёт дело в свои руки, выполняет его в фоне и возвращает результат, когда всё готово. Вы просто отдаёте команду — и отдыхаете! ?.

Пример: "Сделай за меня, Task!"

Task {
    let data = await fetchData()
    print(data)
}

Это примерно, как попросить друга принести вам кофе. Вы просто говорите, что нужно сделать fetchData(), и ждёте await. Когда данные готовы, Task вернёт результат, а вы — спокойно продолжаете свои дела. Удобно, правда?

Пример: "Ой, а если что-то пошло не так?"

Task {
    do {
        let response = await fetchDataFromAPI(url: "https://api.example.com/data")
        print("Данные: \(response)")
    } catch {
        print("Ошибка: \(error)")
    }
}

Ну, бывает, кофе пролили... ой, данные не загрузились. Тут Task работает как бариста, который вместо кофе сообщает: "Извините, кофемашина сломалась!" А вы в catch спокойно подумаете, как обойтись без кофе! ☕

Пример: «Галя! У нас отмена» ?

Если пользователь закрыл экран или отменил действие, задача становится ненужной. С помощью Task.isCancelled можно её вовремя остановить.

Task {
    for i in 0...100 {
        if Task.isCancelled { return } // Проверяем, отменена ли задача
        print(i)
    }
}

Что происходит?

Представьте, что вы печатаете 100 страниц, а вдруг начальник кричит: "Стоп! Всё отменяем!" Тут на помощь приходит Task.isCancelled — вы вовремя останавливаете процесс, чтобы не тратить ресурсы зря. Ловко и экономно

Полезные свойства Task:

Свойство

Описание

Пример

Приоритеты

Указывайте важность задачи

.high, .medium, .low

Отмена

Позволяет остановить выполнение

Task.isCancelled

Связь с контекстом

Наследует окружение, если не detached

Используйте вместо detached Task

?️ Actor: защитник ваших данных от хаоса многопоточности

Вы видели, как два потока одновременно пытаются менять одну переменную? Это как две собаки, дерущиеся за одну палку. Именно для этого придумали Actor.

Пример без Actor: "Кто последний, тот и прав!" ?

class Counter {
    private var count = 0

    func increment() {
        count += 1
    }
}

Что здесь может пойти не так?

Представьте, что два кассира одновременно пытаются посчитать деньги в одной и той же кассе. Один добавляет купюры, другой забирает сдачу, а в итоге сумма становится неправильной. Здесь происходит то же самое: если два потока вызовут increment() одновременно, результат будет непредсказуемым. Ваш счётчик превратится в лотерею. ?

Пример с Actor: "Только по очереди, господа" ?

actor Counter {
    private var count = 0

    func increment() {
        count += 1
    }
}

Что изменилось?

Теперь всё цивилизованно, как в очереди за кофе. Каждый поток вежливо ждёт своей очереди, чтобы увеличить countActor следит за порядком и гарантирует, что никто не влезет без очереди. Результат? Чёткий и предсказуемый. Всё работает, как швейцарские часы! ?️

Пример: "Actor, который защищает ваши деньги" ?

actor BankAccount {
    private var balance: Double = 0.0

    func deposit(amount: Double) {
        balance += amount
    }

    func getBalance() -> Double {
        return balance
    }
}

let account = BankAccount()

Task {
    await account.deposit(amount: 100.0)
    let balance = await account.getBalance()
    print("Ваш баланс: \(balance)")
}

Ваш банковский счёт — это святое, и Actor здесь как персональный охранник. Он заботится, чтобы все операции с балансом были выполнены правильно.

Когда вы кладёте деньги depositActor гарантирует, что никто другой в этот момент не пытается "подсмотреть" или изменить баланс. Когда вы запрашиваете баланс getBalance, вы получаете актуальные данные без риска, что кто-то "вмешался".

Почему это важно? 

С Actor ваши данные защищены, как будто за ними наблюдает строгий охранник. Он гарантирует, что всё будет работать, как задумано, даже если за доступ к данным сражаются десятки потоков.

?️ Structured Concurrency: никакого бардака в задачах

Если ваш асинхронный код раньше напоминал запутанный клубок из вложений, то Structured Concurrency — это как генеральная уборка: всё организовано, задачи разложены по полочкам, и хаос больше не угрожает вашему проекту.

Пример: "Давайте по очереди, как в старые времена"

let image = await loadImage()
let text = await loadText()

Этот код действует как строгая очередь: сначала загружаем изображение loadImage, затем — текст loadText. Всё выполняется по порядку, никакой спешки. Такой подход удобен, если следующий шаг зависит от результата предыдущего. 

Например, сначала вы получаете картинку, а потом текст для её описания. Надёжно и без сюрпризов! ✅

Пример: "Или всё сразу, как на настоящей вечеринке"

async let image = loadImage()
async let text = loadText()

let result = try await (image, text)

Здесь задачи запускаются одновременно, как гости, которые танцуют каждый в своём стиле. Пока одно загружается (изображение), другое (текст) выполняется параллельно. Как только обе задачи завершены, их результаты объединяются с помощью try await.

Этот подход идеально подходит для ускорения работы, если задачи независимы друг от друга. Благодаря Structured Concurrency всё остаётся под контролем: задачи выполняются быстро, а их завершение — упорядочено. Это как вечеринка, где уборкой занимается робот-пылесос. Всё организовано и работает как надо! ?✨

? Классы, структуры и Actor: кто в доме хозяин?

В Swift три типа данных борются за право называться "лучшим выбором для асинхронного кода". Вот их сравнение:

Аспект

Классы

Структуры

Actor

Тип

Ссылочный

Значимый

Ссылочный

Потокобезопасность

Нет

Нет

Да

Применение

Общие данные, наследование

Независимые данные

Потокобезопасное управление данными

Управление памятью

ARC

Выделяется на стеке

ARC с потокобезопасностью

Комментарий:

  1. Если ваши данные простые и никуда не "убегают", выбирайте структуры. Это как тетрадь: легко передать, и никто ничего не испортит. ✍️

  2. Для сложных объектов, которые должны сохранять ссылку на одно и то же состояние, выбирайте классы. Они работают как общий доступ к файлу в облаке. ☁️

  3. Если ваши данные пытаются "раздербанить" несколько потоков, Actor выступает как надёжный охранник. Он пропускает каждого строго по очереди, не допуская хаоса. ?️

Каждый тип хорош для своего случая.  Выбирайте мудро, и ваш код скажет вам спасибо! ?

? Советы, которые спасут вас от головной боли ?

Для новичков:

  1. Task — начните с простого: Попробуйте запускать задачи с Task. Это как доверить помощнику небольшую работу: пока он справляется, вы занимаетесь другими делами. Написать первый асинхронный код — легче, чем вы думаете.

  2. Actor — ваши данные под надёжной охраной: Если несколько потоков хотят "поиграть" с вашими данными, Actor — это как строгий охранник. Он гарантирует, что никто не лезет туда, где не надо. Попробуйте защитить банковский счёт или счётчик — это хороший старт.

  3. async let — ускоряем процесс: Когда вам нужно выполнить несколько независимых задач, используйте async let. Это как готовить ужин: вы можете одновременно варить суп и жарить котлеты, а не ждать, пока закончится одно, чтобы начать другое.

Для опытных разработчиков:

  1. async/await вместо GCD: Забудьте про сложные очереди и вложенные замыкания. async/await делает код линейным и читаемым. Это как переписать запутанную инструкцию на понятный чек-лист.

  2. Structured Concurrency — всё под контролем: Управляйте сложными задачами, как режиссёр управляет съёмочной группой: у каждой задачи своё место и время.

  3. Actor для глобального порядка: Когда у вас сложная система с общими данными, Actor спасёт вас от багов. Это как нанять системного администратора, который следит, чтобы серверы не конфликтовали друг с другом.

Почему это важно?

Эти инструменты не просто делают ваш код быстрее и надёжнее. Они меняют подход к разработке. Начните применять их, и вы поймёте: асинхронный код больше не враг, а ваш лучший союзник! ?

? Заключение: Swift Concurrency — ваш новый друг в мире асинхронности

Swift Concurrency — это свежий взгляд на асинхронное программирование. Теперь код читается как увлекательная книга, а не как инструкция по сборке шкафа из замыканий и GCD. "Пирамида смерти"? Забудьте. Сложные цепочки? Это прошлое.

Попробуйте Swift Concurrency в своём следующем проекте. Ваш будущий опытный "я" скажет вам спасибо! ?

? Полезные ссылки:

  1. Официальная документация Swift Concurrency

  2. Видео с WWDC: Что нового в Swift Concurrency

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


  1. SysoevAndrey
    03.12.2024 06:42

    Спасибо за статью! Кажется, в секции Пример: "Или всё сразу, как на настоящей вечеринке" потерялся сниппет с примером async let