← Назад к вопросам

Как реализовать потокобезопасность?

2.3 Middle🔥 181 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать потокобезопасность в iOS-разработке

Потокобезопасность — это гарантия корректной работы объектов и структур данных при одновременном доступе из нескольких потоков. В iOS-разработке это критически важно, поскольку UIKit и многие системные фреймворки требуют работы в главном потоке, а параллельные операции (сеть, вычисления, база данных) выполняются в фоновых потоках.

Основные подходы к обеспечению потокобезопасности

1. Serial Dispatch Queue (Последовательная очередь)

Самый распространённый и рекомендуемый Apple подход — использование GCD (Grand Central Dispatch). Создавая последовательную очередь, вы гарантируете, что все операции с объектом выполняются строго одна за другой.

class ThreadSafeDataManager {
    private let serialQueue = DispatchQueue(label: "com.example.serialQueue")
    private var internalData: [String: Any] = [:]
    
    func setValue(_ value: Any, forKey key: String) {
        serialQueue.async {
            self.internalData[key] = value
        }
    }
    
    func getValue(forKey key: String, completion: @escaping (Any?) -> Void) {
        serialQueue.async {
            completion(self.internalData[key])
        }
    }
}

2. Isolated Queues с барьерными операциями

Для конкурентных очередей, где чтение может быть параллельным, а запись — эксклюзивной, используют барьерные операции (barrier).

class ThreadSafeCache {
    private let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", 
                                                attributes: .concurrent)
    private var cache: [String: Data] = [:]
    
    func getData(for key: String) -> Data? {
        var data: Data?
        concurrentQueue.sync { // Чтение — параллельно
            data = cache[key]
        }
        return data
    }
    
    func setData(_ data: Data, for key: String) {
        concurrentQueue.async(flags: .barrier) { // Запись — эксклюзивно
            self.cache[key] = data
        }
    }
}

3. NSLock и NSRecursiveLock

Низкоуровневые примитивы синхронизации. NSLock для простых случаев, NSRecursiveLock когда один поток может захватить lock несколько раз (например, в рекурсивных функциях).

class ThreadSafeCounter {
    private var count =450
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        defer { lock.unlock() } // Гарантирует разблокировку даже при исключении
        
        count += 1
    }
    
    func getCount() -> Int {
        lock.lock()
        defer { lock.unlock() }
        
        return count
    }
}

4. Атомарные свойства с @propertyWrapper

Начиная с Swift 5.1 можно создать кастомный property wrapper для атомарного доступа.

@propertyWrapper
struct Atomic<Value> {
    private var value: Value
    private let lock = NSLock()
    
    init(wrappedValue: Value) {
        self.value = wrappedValue
    }
    
    var wrappedValue: Value {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
}

class UserSettings {
    @Atomic var lastLoginDate: Date = Date()
    @Atomic var loginCount: Int = 0
}

5. Actor (Swift 5.5+)

Современный подход с использованием акторов, которые обеспечивают изоляцию данных на уровне языка.

actor BankAccount {
    private var balance: Double = 0
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Bool {
        if balance >= amount {
            balance -= amount
            return true
        }
        return false
    }
    
    func currentBalance() -> Double {
        return balance
    }
}

// Использование
Task {
    let account = BankAccount()
    await account.deposit(amount: 1000)
    let balance = await account.currentBalance()
}

Ключевые рекомендации

  • Избегайте гонок данных (race conditions) — всегда синхронизируйте запись и чтение разделяемых ресурсов
  • Минимизируйте область синхронизации — блокируйте только критическую секцию, не весь метод
  • Избегайте взаимных блокировок (deadlocks) — особенно при использовании нескольких lock'ов
  • Тестируйте многопоточность — используйте инструменты вроде Thread Sanitizer в Xcode
  • Предпочитайте высокоуровневые подходы — GCD и Actors обычно безопаснее и производительнее низкоуровневых lock'ов
  • Помните про главный поток — обновление UI всегда должно быть на DispatchQueue.main

Практические паттерны

  1. Иммутабельность — создание неизменяемых объектов, которые безопасно разделяются между потоками
  2. Копирование перед модификацией — особенно для коллекций
  3. Thread confinement — привязка объекта к конкретному потоку (как UIKit работает с главным)

Выбор подхода зависит от конкретного случая: Actors для современного async/await кода, GCD для общего использования, NSLock для микрооптимизаций в performance-critical секциях. Всегда оценивайте компромисс между безопасностью, производительностью и сложностью кода.

Как реализовать потокобезопасность? | PrepBro