Что такое многопоточность (multithreading) в iOS и какие инструменты для нее используются?

«Что такое многопоточность (multithreading) в iOS и какие инструменты для нее используются?» — вопрос из категории Многопоточность, который задают на 28% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Многопоточность — это техника, позволяющая приложению выполнять несколько задач (потоков) параллельно или псевдопараллельно (через быстрое переключение контекста). Это необходимо для:

  1. Отзывчивого UI (главный поток не блокируется долгими операциями).
  2. Эффективного использования многоядерных процессоров.
  3. Выполнения фоновых задач (сеть, вычисления, I/O).

Основные инструменты в iOS/macOS:

Инструмент Уровень Основная концепция Когда использовать
Grand Central Dispatch (GCD) Низкоуровневый C API Очереди (DispatchQueue) задач (closures). Большинство задач: фоновая работа, синхронизация, таймеры.
OperationQueue Высокоуровневый Objective-C/Swift API Операции (Operation) — абстракция задачи с зависимостями, отменой, приоритетом. Сложные задачи с зависимостями, требующие отмены или наблюдения за состоянием.
Thread (NSThread) Самый низкий уровень Напрямую управляемые потоки POSIX. Редко. Только для специфичных, долгих задач, где нужен полный контроль над жизненным циклом потока.

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

// 1. Выполнение задачи в фоне
DispatchQueue.global(qos: .userInitiated).async {
    // Эта closure выполняется в фоновом потоке
    let processedData = performIntensiveCalculation()

    // 2. Возврат результата в главный поток для обновления UI
    DispatchQueue.main.async {
        self.updateUI(with: processedData)
    }
}

Ключевые понятия и проблемы:

  • Гонка данных (Race Condition): Несколько потоков одновременно обращаются и изменяют общие данные без синхронизации, приводя к неопределенному поведению.
    • Решение: Использовать очереди (серийные или с барьерами), семафоры (DispatchSemaphore), замки или акторы (Actor).
  • Взаимная блокировка (Deadlock): Два или более потока бесконечно ждут друг друга, освобождая ресурсы.
    • Пример: Вызов sync на текущей очереди.
  • Главный поток (Main Thread): Весь пользовательский интерфейс (UI) должен обновляться только на главном потоке. Блокировка этого потока приводит к "зависанию" интерфейса.

Современный подход (Swift Concurrency): Начиная со Swift 5.5, рекомендуется использовать async/await, акторы (Actor) и структурированный параллелизм (Task, TaskGroup). Эти инструменты безопаснее и выразительнее, чем ручное управление потоками через GCD.

// Пример современного подхода
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 { // Безопасный переход на главный актор
        self.updateUI(with: data)
    }
}