Является ли Run Loop в 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()
    }
}

Ответ 18+ 🔞

А, ну ты про Run Loop, да? Ну слушай, тут история простая, но пиздец важная, как не влететь на ровном месте. Представь себе, что у каждого потока — свой личный, блядь, унитаз. И ты можешь срать только в свой. Понял? Нельзя прийти в чужой поток и начать там кнопки нажимать, воду спускать или хуй знает что ещё.

Run Loop — он не для всех, сука. У каждого потока (включая главного, этого выебосса) максимум один свой RunLoop. И трогать его можно только из того же потока, где он родился. Попробуешь из другого потока в него влезть — получишь неопределённое поведение, краши и волосы, вырванные нахуй с корнем.

Короче, правило железное: Все дела с RunLoop.current делай в том же потоке, где ты его и взял. Не выёбывайся.

Вот смотри, как НЕ НАДО, дебил:

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

А вот как надо, умник:

let backgroundThread = Thread {
    // 1. Берём RunLoop ТЕКУЩЕГО (фонового) потока. Тут, блядь, в его же доме.
    let runLoop = RunLoop.current

    // 2. Кидаем ему какую-нибудь хуйню, чтобы он не уснул сразу от скуки. Например, порт.
    runLoop.add(Port(), forMode: .default)

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

А если тебе нужно потоки между собой общаться? Не лезь со своим RunLoop'ом в чужой монастырь! Есть для этого нормальные, блядь, инструменты:

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

Запомни: Run Loop — это не общий горшок, а личный ночной горшок каждого потока. Не путай.