Ответ
Нет, 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 — это не общий горшок, а личный ночной горшок каждого потока. Не путай.