Является ли Run Loop в iOS потокобезопасным?

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

Ответ

Нет, Run Loop не является потокобезопасным. Каждый поток (включая главный) имеет максимум один связанный с ним объект RunLoop, и работать с ним можно только в контексте этого потока.

Почему это важно: Попытка вызова методов RunLoop (таких как run(), add(_:forMode:)) из потока, отличного от того, которому он принадлежит, приводит к неопределенному поведению и часто к крашам.

Правило: Все манипуляции с RunLoop.current должны выполняться в том же потоке, где этот RunLoop был получен.

Пример опасного кода:

DispatchQueue.global().async {
    // ОШИБКА: Попытка запустить RunLoop фонового потока не из него самого.
    // Это может заблокировать поток или привести к сбою.
    RunLoop.current.run(mode: .default, before: .distantFuture)
}

Правильный подход для фоновых потоков:

let backgroundThread = Thread {
    // 1. Получаем RunLoop ТЕКУЩЕГО (фонового) потока.
    let runLoop = RunLoop.current

    // 2. Добавляем источник событий (например, порт), чтобы RunLoop не завершился сразу.
    runLoop.add(Port(), forMode: .default)

    // 3. Запускаем RunLoop В ЭТОМ ЖЕ потоке.
    // RunLoop будет обрабатывать события, пока не будет остановлен.
    runLoop.run()
}
backgroundThread.start()

Для межпоточного взаимодействия вместо прямого управления Run Loop другого потока используйте механизмы, предназначенные для этого:

  • GCD (DispatchQueue): async, sync.
  • OperationQueue.
  • performSelector(on:with:waitUntilDone:) — это безопасный способ поставить задачу в очередь на выполнение в Run Loop целевого потока.
// Безопасное выполнение кода в RunLoop главного потока из фонового:
DispatchQueue.global().async {
    // ... фоновая работа ...
    DispatchQueue.main.async {
        // Код выполнится в RunLoop главного потока.
        self.updateUI()
    }
}