Какие инструменты для многопоточности доступны в iOS/macOS?

Ответ

Основные инструменты для работы с многопоточностью в экосистеме Apple:

  1. Grand Central Dispatch (GCD / libdispatch): Низкоуровневый C-фреймворк для управления очередями задач.

    • Очереди: DispatchQueue (serial, concurrent), DispatchGroup, DispatchSemaphore.
    • Пример:
      // Отправка задачи в фоновую очередь
      DispatchQueue.global(qos: .userInitiated).async {
      let result = performHeavyCalculation()
      // Возврат результата в главный поток для обновления UI
      DispatchQueue.main.async {
          updateUI(with: result)
      }
      }
  2. OperationQueue: Высокоуровневая абстракция на основе GCD. Позволяет создавать переиспользуемые Operation с зависимостями, приоритетами и возможностью отмены.

    let queue = OperationQueue()
    queue.maxConcurrentOperationCount = 2
    
    let downloadOp = BlockOperation { /* Загрузка данных */ }
    let processOp = BlockOperation { /* Обработка данных */ }
    processOp.addDependency(downloadOp) // Обработка начнется только после загрузки
    
    queue.addOperations([downloadOp, processOp], waitUntilFinished: false)
  3. Swift Concurrency (async/await): Современный, безопасный подход, представленный в Swift 5.5.

    • Ключевые элементы: async/await, Task, Actor.
    • Пример:
      
      func fetchData() async throws -> Data {
      let (data, _) = try await URLSession.shared.data(from: url)
      return data
      }

    Task { let data = try await fetchData() await MainActor.run { updateUI(with: data) } }

  4. Средства синхронизации: Для защиты общих ресурсов от состояний гонки.

    • NSLock, os_unfair_lock (более эффективный).
    • @Atomic свойства (через property wrappers).
    • Актёры (Actors) в Swift Concurrency.

Рекомендация: Для нового кода предпочтительно использовать Swift Concurrency, так как он обеспечивает безопасность на уровне компилятора и более читаемый код.

Ответ 18+ 🔞

А, ну вот, смотри, классика жанра, блядь! Многопоточность на iOS — это как цирк с конями, только лошади все сдохли, а клоуны разбежались, и ты остаёшься один на арене с этими хуёвыми шариками. Но ладно, разберёмся, что у нас тут есть.

Во-первых, старый добрый Grand Central Dispatch (GCD), он же libdispatch. Это как дед, который всё ещё может вломить, но уже путает таблетки. Низкоуровневый такой, на C, но обёрнут в красивый DispatchQueue. Суть проста, как три копейки: есть очередь, ты кидаешь в неё задачу — она выполняется. Очереди бывают серийные (одна за другой, как в очереди за водкой) и конкурентные (все лезут напролом, как в маршрутку). И главное правило, которое забывают все распиздяи: UI обновлять ТОЛЬКО с главной очереди, иначе приложение накроется медным тазом, и пользователь увидит дичь вместо интерфейса.

// Кидаем тяжёлую хуйню в фон
DispatchQueue.global(qos: .userInitiated).async {
    let result = performHeavyCalculation() // тут твой код грузит ядро
    // А теперь, внимание, магия: возвращаемся на главную, как барин в усадьбу
    DispatchQueue.main.async {
        updateUI(with: result) // и только тут тыкаем в интерфейс
    }
}

Дальше идёт OperationQueue — это уже для интеллигентных ребят, которые любят планировать. Типа, ты создаёшь операции (Operation), можешь между ними зависимости выстроить (сначала скачай, потом распакуй, потом вдуй), приоритеты назначить и даже отменить всё это дело, если передумал. Удобно, когда задач дохуища и они сложные. Максимум параллелизма можно ограничить, чтобы не угробить систему.

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // чтобы не распиздяйничать, только две задачи одновременно

let downloadOp = BlockOperation { /* Качаем файлы, блядь */ }
let processOp = BlockOperation { /* Обрабатываем, епта */ }
processOp.addDependency(downloadOp) // Сначала качаем, потом обрабатываем — логика, ёпта!

queue.addOperations([downloadOp, processOp], waitUntilFinished: false) // и поехали

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

func fetchData() async throws -> Data {
    // Никаких completionHandler, блядь! Просто ждём, пока данные придут.
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

Task { // Запускаем новую таску
    let data = try await fetchData() // Ждём, не дергаясь
    await MainActor.run { updateUI(with: data) } // И на главный актор — обновляем UI
}

И, конечно, куда же без синхронизации, а? Потому что если несколько потоков полезют в одну переменную, будет пиздец, состояние гонки, и значения будут как у шлюхи — каждый раз разные. Раньше мучались с NSLock или os_unfair_lock, писали обёртки @Atomic. А теперь, ёпта, есть акторы (Actors) в Swift Concurrency! Это такие изолированные хуйни, которые сами следят, чтобы к их данным был доступ только из одного потока за раз. Красота, блядь!

Итог, Колян: Если пишешь новый проект — даже не думай, блядь, бери Swift Concurrency. Это как перейти с каменного топора на бензопилу. Код чище, безопаснее, и голова болит меньше. А старые GCD и OperationQueue оставь для легаси-кода или очень специфичных случаев, когда надо по-старинке, с чувством, с толком, с расстановкой.