Как безопасно обновлять массив из фонового потока в Swift?

«Как безопасно обновлять массив из фонового потока в Swift?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, можно обновлять массив из фонового потока, но массивы Swift не являются потокобезопасными. Прямое изменение из нескольких потоков вызывает race condition и может крашить приложение.

Решение 1: GCD с барьером (iOS 4+)

class ThreadSafeArray<T> {
    private var array: [T] = []
    private let queue = DispatchQueue(label: "com.app.threadSafeArray", 
                                    attributes: .concurrent)

    func append(_ element: T) {
        queue.async(flags: .barrier) {
            self.array.append(element)
            // Обновление UI должно быть на main
            DispatchQueue.main.async {
                // UI обновления здесь
            }
        }
    }

    var values: [T] {
        queue.sync { array } // Безопасное чтение
    }
}

Решение 2: Actor (Swift 5.5+, iOS 13+)

actor SafeArray<T> {
    private var array: [T] = []

    func append(_ element: T) {
        array.append(element)
    }

    func getAll() -> [T] {
        return array
    }
}

// Использование:
let safeArray = SafeArray<String>()
Task {
    await safeArray.append("Item from background")
    let items = await safeArray.getAll()
}

Решение 3: @MainActor для UI-обновлений

@MainActor
class ViewModel: ObservableObject {
    @Published var items: [String] = []

    func fetchData() async {
        let newItems = await fetchFromBackground()
        // Автоматически на главном потоке благодаря @MainActor
        items.append(contentsOf: newItems)
    }
}

Критические правила:

  1. Все модификации массива должны быть синхронизированы
  2. Чтение тоже требует синхронизации (race condition возможен при чтении во время записи)
  3. Для UI-обновлений всегда переключайтесь на главный поток
  4. Используйте NSLock или os_unfair_lock для микрооптимизаций
  5. Рассмотрите готовые решения вроде Atomic из Swift Collections