Могут ли несколько потоков одновременно изменять один объект в Swift?

Ответ

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

Пример проблемы (гонка данных):

var counter = 0
DispatchQueue.concurrentPerform(iterations: 100) { _ in
    counter += 1 // Небезопасно: несколько потоков читают и пишут одновременно
}
print(counter) // Результат может быть меньше 100

Решение — использование механизмов синхронизации:

  • DispatchQueue с барьером (barrier) или синхронным доступом (sync) для сериализации операций записи.
  • NSLock / os_unfair_lock для явной блокировки.
  • @MainActor для безопасного обновления UI из разных потоков.
  • Actor (Swift 5.5+) — современный тип, обеспечивающий изоляцию.

Пример с Actor:

actor SafeCounter {
    private var value = 0
    func increment() { value += 1 }
    func getValue() -> Int { value }
}

// Использование:
let counter = SafeCounter()
Task {
    await counter.increment() // Доступ изолирован, гонок нет
}

Ответ 18+ 🔞

А, слушай, тут один умник спрашивает, можно ли в Swift писать в одну переменную из нескольких потоков. Ну, внатуре, можно, конечно, блядь! Только потом не удивляйся, что у тебя данные похожи на фарш после мясорубки, которую держали десять пьяных слесарей.

Это, блядь, классическая гонка, race condition называется. Состояние такое, ёпта, неопределённое — сегодня отработало, завтра словил креш, послезавтра значение как из жопы левой ноги вылезло. Полный пиздец, короче.

Смотри, вот тебе наглядный пиздец в коде:

var counter = 0
DispatchQueue.concurrentPerform(iterations: 100) { _ in
    counter += 1 // А вот тут-то и начинается цирк, блядь! Все лезут разом!
}
print(counter) // И получишь ты не 100, а какую-нибудь хуйню вроде 87 или 93. Удивление пиздец!

Видишь? Все потоки как мартышлюшки на банан кинулись — читают значение, каждый своё дерьмо прибавляет и обратно пишет. Итог — волнение ебать, а доверия к результату — ноль ебать.

Так что делать-то, спрашиваешь? Ну, вариантов, блядь, овердохуища, как синхронизировать этот бардак.

Можно старым добрым способом, через DispatchQueue с барьером или просто синхронно всё делать, чтобы очередь была. Можно замками (NSLock) махаться, как в старые добрые, чтоб только один в этот момент в сортир заходил.

Для UI-обновлений, там, @MainActor — святое дело, чтоб не вышло, что интерфейс рвётся из всех щелей одновременно.

Но самый, блядь, модный и правильный способ сейчас — это Actor. Свифт 5.5 его впендюрил, и слава богу. Это такой тип, который сам за собой следит, чтобы к его внутренностям только один поток в один момент времени лапал. Красота!

Вот, смотри, как красивше:

actor SafeCounter {
    private var value = 0
    func increment() { value += 1 }
    func getValue() -> Int { value }
}

// Использование:
let counter = SafeCounter()
Task {
    await counter.increment() // Всё, блядь, изолированно. Никаких гонок, никакого хаоса. Тишина и покой.
}

Вот и вся магия. Хочешь без головной боли — используй акторы. Не хочешь — ну, страдай потом, дебажь эти ебушки-воробушки с гонками, чих-пых тебя в сраку. Выбор за тобой.