Ответ
Нет. Классы в 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.
Способы обеспечения потокобезопасности:
-
Изоляция через очередь (
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 } } } - Акторы (
actor, Swift 5.5+): Автоматическая изоляция состояния. - Примитивы синхронизации:
NSLock,os_unfair_lock, семафоры. - Атомарные операции: Использование
Atomicproperty 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 поверх. В итоге вместо двух инкрементов — один. И так по тысяче раз. Данные — в говне.
Так как же, блядь, сделать чтобы не было стыдно? Варианты есть:
-
Старый добрый
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 } } }Работает, проверено. Но писать это каждый раз — терпения ебать ноль.
-
Акторы (
actor), ёпта! Это же Swift 5.5+. Святое дело.actor ProperSafeCounter { private var value = 0 func increment() { value += 1 } func getValue() -> Int { return value } }Всё. Компилятор сам проследит, чтобы к
valueне лезли одновременно. Красота, блядь. Как по маслу. -
Различные замки (
NSLock,os_unfair_lock). Прям как в тюрьме. Захотел войти — захлопнул за собой дверь, сделал дело, вышел. Пока ты внутри, все остальные ждут, как лохи. Но с замками надо аккуратнее, а то забудешь отпустить — и будет deadlock, вечная блокировка, пиздец всем. -
Атомарные операции. Для особых ценителей, кто любит ковыряться в низкоуровневом железе. Раньше были
OSAtomic, сейчас есть другие штуки. Суть в том, что операция "прочитать-изменить-записать" делается за один такт, неразрывно. Но это уже для сложных случаев, когда от производительности ебаться.
Вывод, Колян: Не верь никому, кто говорит, что с классами всё просто. Если твой объект шарится между потоками — либо изолируй его (очередь, актор), либо делай иммутабельным (чтобы менять было нельзя в принципе), либо готовься к волшебным и необъяснимым багам в продакшене. Всё просто, блядь.