Приведи пример использования async/await для сетевого запроса в Swift.

Ответ

async/await — это модель асинхронного программирования в Swift, позволяющая писать асинхронный код в линейном, легко читаемом стиле, без цепочек completion handlers (callbacks).

Базовый пример сетевого запроса:

// 1. Объявление асинхронной функции
func fetchUserData(from url: URL) async throws -> User {
    // 2. `await` приостанавливает функцию, пока не будет получен ответ,
    // но не блокирует поток.
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw NetworkError.invalidResponse
    }
    // 3. Декодирование также может быть асинхронной операцией.
    let user = try JSONDecoder().decode(User.self, from: data)
    return user
}

// 4. Вызов асинхронной функции.
// Должен происходить внутри асинхронного контекста: `Task` или другой `async` функции.
Task {
    do {
        let url = URL(string: "https://api.example.com/user/1")!
        let user = try await fetchUserData(from: url)
        print("User received: (user.name)")
        // 5. Обновление UI должно выполняться на главном потоке.
        await MainActor.run {
            self.nameLabel.text = user.name
        }
    } catch {
        print("Failed to fetch user: (error)")
    }
}

Пример параллельного выполнения запросов (Concurrency):

func fetchUserAndPosts(userId: Int) async throws -> (User, [Post]) {
    async let user = fetchUserData(from: userURL) // Запускаем асинхронно
    async let posts = fetchPostsData(from: postsURL) // Запускаем асинхронно
    // Оба запроса выполняются параллельно.
    // `try await` ожидает завершения обоих.
    return try await (user, posts)
}

Ключевые концепции:

  • async — помечает функцию как асинхронную. Она может быть приостановлена.
  • await — точка возможной приостановки, где функция ожидает результат асинхронной операции.
  • Task — единица работы, которая выполняется асинхронно. Создает асинхронный контекст.
  • throws — асинхронные функции могут выбрасывать ошибки, как и обычные.
  • MainActor — глобальный актор, гарантирующий выполнение кода на главном потоке. Используется для обновления UI.

Ответ 18+ 🔞

Э, слушай, смотри, вот эта вся хуйня с async/await в Swift — это типа как взять свои старые кошмарные колбэки, которые в три этажа, и выкинуть их нахуй в окно. Вместо этого пишешь код, который выглядит почти как обычный, линейный, но при этом он нихуя не блокирует твой главный поток, пока там что-то грузится из интернета. Красота, ёпта!

Смотри, как это выглядит на практике, вот тебе классика жанра — запрос в сеть:

// 1. Вот объявляешь функцию и пишешь `async`. Это как знак "осторожно, тут можно вздремнуть".
func fetchUserData(from url: URL) async throws -> User {
    // 2. А вот тут магия! `await` — это не блокировка, это вежливое "я подожду тут, на обочине, пока ты, URLSession, не притащишь мне данные".
    // Поток в это время не стоит столбом, он может другим заняться. Умно, блядь.
    let (data, response) = try await URLSession.shared.data(from: url)

    // Ну тут обычная проверка, не обосрался ли запрос.
    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw NetworkError.invalidResponse
    }
    // 3. Декодируем JSON. Тоже может быть асинхронным, если данных овердохуища.
    let user = try JSONDecoder().decode(User.self, from: data)
    return user
}

// 4. А теперь, чтобы эту красоту вызвать, нужен асинхронный контекст. Засовываем всё в `Task`.
Task {
    do {
        let url = URL(string: "https://api.example.com/user/1")!
        // Ждём-с результат. Опять `await`.
        let user = try await fetchUserData(from: url)
        print("User received: (user.name)")
        // 5. ВАЖНО! UI обновлять нужно на главном потоке. Для этого есть `MainActor`.
        // Спрашиваешь у главного актора: "Браток, сделай мне одолжение, обнови label".
        await MainActor.run {
            self.nameLabel.text = user.name
        }
    } catch {
        // Ну и если всё пошло по пизде, ловим ошибку.
        print("Failed to fetch user: (error)")
    }
}

А вот это вообще пиздец как удобно — запустить два запроса параллельно, а не ждать как лох по очереди:

func fetchUserAndPosts(userId: Int) async throws -> (User, [Post]) {
    // Смотри, какая хитрая жопа! `async let` — это как сказать: "Эй, чувак, начинай качать юзера, а ты, второй чувак, начинай качать посты. Работайте!"
    async let user = fetchUserData(from: userURL)
    async let posts = fetchPostsData(from: postsURL)
    // А тут мы просто ждём, пока оба этих молодца закончат. Они работают одновременно, ёба!
    return try await (user, posts)
}

Короче, основные плюшки, чтобы не быть мудаком:

  • async — клеймо на функции. Говорит: "Внутри меня может быть await, я умею засыпать и просыпаться".
  • await — точка, где твоя функция говорит "я тут постою, подожду результата, не мешайте". Это НЕ БЛОКИРОВКА потока, запомни, ебать!
  • Task — это как коробка, в которую ты засовываешь асинхронную работу, чтобы она вообще запустилась.
  • throws — да, асинхронные функции тоже могут выбросить тебе ошибку в ебло, если что-то пойдёт не так.
  • MainActor — твой главный по тарелочкам. Гарантирует, что всё, что внутри run, выполнится на главном потоке. Без него лезть в UI из асинхронного кода — это прямой путь в крематорий для приложения.