Являются ли классы в Swift потокобезопасными по умолчанию?

Ответ

Нет. Классы в Swift (как и в большинстве языков) не являются потокобезопасными по умолчанию.

Экземпляр класса — это разделяемый изменяемый ресурс. Если несколько потоков одновременно обращаются к одному экземпляру и хотя бы один поток выполняет запись, возникает гонка данных (race condition). Это приводит к неопределённому поведению, сбоям (EXC_BAD_ACCESS) или повреждению данных.

Пример проблемы:

class UnsafeCounter {
    var value = 0
    func increment() { value += 1 } // НЕ атомарная операция!
}

let counter = UnsafeCounter()
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter.increment() // ❌ Множественные потоки — гонка данных.
}
print(counter.value) // Результат непредсказуем, меньше 1000.

Способы обеспечения потокобезопасности:

  1. Изоляция через очередь (DispatchQueue):

    class ThreadSafeCounter {
        private var count = 0
        private let queue = DispatchQueue(label: "sync.queue", attributes: .concurrent)
    
        func increment() {
            queue.async(flags: .barrier) { // Барьер для записи
                self.count += 1
            }
        }
    
        var currentValue: Int {
            queue.sync { // Безопасное чтение
                return count
            }
        }
    }
  2. Акторы (actor, Swift 5.5+): Автоматическая изоляция состояния.
  3. Примитивы синхронизации: NSLock, os_unfair_lock, семафоры.
  4. Атомарные операции: Использование Atomic property wrappers или низкоуровневых функций (OSAtomic).

Ответ 18+ 🔞

О, а вот это, блядь, классика! Сидишь такой, думаешь: "Ну класс, ёпта, объектик создал, всё красиво". А потом — бац! — и прилетает тебе в лоб EXC_BAD_ACCESS, будто хуй с горы. И сидишь, чешешь репу: "Что за нахуй? Я ж всего-то значение поменял!"

Так вот, слушай сюда, распиздяй. Классы в Swift — они по умолчанию вообще ни разу не потокобезопасные. Это как оставить на столе пачку денег в открытой комнате, где орёт толпа гопников. Один возьмёт сотку, другой — пятирублёвую монету, третий вообще всю пачку спиздит, а четвёртый, блядь, записку "спасибо" оставит. Итог? Пиздец и бардак.

Смотри, как это выглядит на практике:

class CounterForDumbasses {
    var value = 0
    func addOne() { value += 1 } // О, смотри какая простая операция! А нихуя не атомарная!
}

let counter = CounterForDumbasses()
// Запускаем тысячу потоков, пусть все дружно добавляют!
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter.addOne() // ❌ Вот тут-то и начинается пиздец. Гонка данных, мать её.
}
print(counter.value) // И что мы получим? Хуй знает! 850, 999, а может, и 42. Угадай, ебушки-воробушки.

Видишь? Несколько потоков лезут в одну переменную value как мартышлюшки на банан. Один поток только начал читать старое значение (допустим, 5), второй уже успел его увеличить и записать 6, а первый, еблан, по старой памяти пишет своё 6 поверх. В итоге вместо двух инкрементов — один. И так по тысяче раз. Данные — в говне.

Так как же, блядь, сделать чтобы не было стыдно? Варианты есть:

  1. Старый добрый DispatchQueue с барьером. Сделаем свою очередь-вышибалу.

    class MoreOrLessSafeCounter {
        private var secretValue = 0
        // Делаем очередь concurrent, но будем ставить барьеры на запись.
        private let guardQueue = DispatchQueue(label: "com.myapp.sync", attributes: .concurrent)
    
        func safeIncrement() {
            guardQueue.async(flags: .barrier) { // Барьер — это стоп-кран. Пока я пишу, все стоят и ждут.
                self.secretValue += 1
            }
        }
    
        var currentValue: Int {
            // А читать-то можно всем сразу! Поэтому sync без барьера.
            return guardQueue.sync {
                return secretValue
            }
        }
    }

    Работает, проверено. Но писать это каждый раз — терпения ебать ноль.

  2. Акторы (actor), ёпта! Это же Swift 5.5+. Святое дело.

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

    Всё. Компилятор сам проследит, чтобы к value не лезли одновременно. Красота, блядь. Как по маслу.

  3. Различные замки (NSLock, os_unfair_lock). Прям как в тюрьме. Захотел войти — захлопнул за собой дверь, сделал дело, вышел. Пока ты внутри, все остальные ждут, как лохи. Но с замками надо аккуратнее, а то забудешь отпустить — и будет deadlock, вечная блокировка, пиздец всем.

  4. Атомарные операции. Для особых ценителей, кто любит ковыряться в низкоуровневом железе. Раньше были OSAtomic, сейчас есть другие штуки. Суть в том, что операция "прочитать-изменить-записать" делается за один такт, неразрывно. Но это уже для сложных случаев, когда от производительности ебаться.

Вывод, Колян: Не верь никому, кто говорит, что с классами всё просто. Если твой объект шарится между потоками — либо изолируй его (очередь, актор), либо делай иммутабельным (чтобы менять было нельзя в принципе), либо готовься к волшебным и необъяснимым багам в продакшене. Всё просто, блядь.