Можно ли безопасно использовать общие ресурсы между разными RunLoop в iOS?

Ответ

Да, но доступ должен быть синхронизирован. Каждый RunLoop привязан к своему потоку. Прямой доступ к общему ресурсу из нескольких потоков (и, следовательно, RunLoop) без синхронизации приводит к состоянию гонки (race condition) и неопределенному поведению.

Способы синхронизации:

  1. Serial DispatchQueue: Гарантирует последовательный доступ.
  2. Concurrent DispatchQueue с барьером: Для эффективного чтения/записи.
  3. Мьютексы (NSLock, os_unfair_lock): Для низкоуровневой синхронизации.

Пример с использованием DispatchQueue:

// Потокобезопасный контейнер
class SharedResource<T> {
    private var value: T
    private let queue = DispatchQueue(label: "com.example.shared", attributes: .concurrent)

    init(_ value: T) { self.value = value }

    func read() -> T {
        return queue.sync { value }
    }

    func write(_ newValue: T) {
        queue.async(flags: .barrier) { self.value = newValue }
    }
}

Вывод: Сам RunLoop не обеспечивает синхронизацию. Используйте стандартные механизмы многопоточности для защиты общих данных.

Ответ 18+ 🔞

А, ну тут, блядь, классика жанра! Слушай, как есть. Вот ты говоришь — «да, но доступ должен быть синхронизирован». Ну да, а как же иначе-то, ёпта? А то сейчас начнётся: один поток пишет, другой читает, третий вообще мимо проходил и случайно ногой задел — и всё, пиздец, приехали. Race condition, неопределённое поведение, краш, слезы, сопли, и вся хуйня.

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

Ну и как с этим бороться, спросишь ты? А вот как, слушай сюда, вариантов — овердохуища:

  1. Serial DispatchQueue. Это как очередь в одну кассу в совковом магазине. Все стоят, ждут, никто не лезет. Гарантирует, что доступ будет строго по одному, последовательно. Просто, надёжно, иногда медленно, если народу много.
  2. Concurrent DispatchQueue с барьером. Это уже похитрее. Читателей — хоть толпу запускай, они все одновременно могут смотреть. Но как только пришёл писатель (барьерная задача) — все, стоп, блядь! Все читатели ждут, пока этот один чувак всё запишет, и только потом снова пускают толпу. Эффективно, умно.
  3. Мьютексы всякие (NSLock, os_unfair_lock). Ну это уже низкоуровневая ручная работа, как с гаечным ключом. Захватил замок — работай, отпустил — другой может взять. Мощно, но можно и палец прищемить, если неаккуратно.

Вот, смотри, пример на DispatchQueue, чтоб понятнее было:

// Потокобезопасный контейнер (чтобы не было как в том анекдоте про Герасима)
class SharedResource<T> {
    private var value: T // Само ценное, за чем все гоняются
    private let queue = DispatchQueue(label: "com.example.shared", attributes: .concurrent) // Наша хитрая очередь

    init(_ value: T) { self.value = value }

    // Почитать может кто угодно, одновременно
    func read() -> T {
        return queue.sync { value } // sync — чтобы сразу результат вернул
    }

    // А чтобы записать — все, стопэ, блядь, барьер!
    func write(_ newValue: T) {
        queue.async(flags: .barrier) { self.value = newValue } // async с барьером — пиши, пока все ждут
    }
}

И главный вывод, который в башку надо вбить: Сам по себе RunLoop — него синхронизацию не обеспечивает, ни хуя. Он просто цикл в потоке. Всё, что между потоками — это твоя головная боль. Так что не надейся на авось, используй нормальные, человеческие механизмы многопоточности, чтобы общие данные не превратились в помойку. А то будет потом: «ой, а у меня тут значение иногда не то» — да потому что ты, блядь, его в состоянии гонки десять раз перезаписал, вот оно и не то!