Ответ
Да, можно обновлять массив из фонового потока, но массивы 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)
}
}
Критические правила:
- Все модификации массива должны быть синхронизированы
- Чтение тоже требует синхронизации (race condition возможен при чтении во время записи)
- Для UI-обновлений всегда переключайтесь на главный поток
- Используйте
NSLockилиos_unfair_lockдля микрооптимизаций - Рассмотрите готовые решения вроде
Atomicиз Swift Collections
Ответ 18+ 🔞
О, слушай, смотри, вот тут такая история, блядь. Ты там в фоне массивы пытаешься пинать, да? Ну, типа, пока юзер пальцем по экрану водит, ты в тихую, на заднем плане, данные подгружаешь и в массив их суёшь. Так вот, запомни раз и навсегда, как отче наш: массивы в Swift — это не потокобезопасные штуки, блядь! Возьмёшь и начнёшь с разных потоков в одну кучу лезть — получишь race condition, а потом будешь чесать репу, отчего твое приложение накрылось медным тазом и вылетело с крашем. Пиздец, а не отладка.
Ладно, не кипятись. Вот тебе несколько способов, как это сделать, чтобы не обосраться.
Вариант первый: Старая добрая GCD с барьером (для дедов, у которых проект с iOS 4+)
Смысл в чём: делаем свою обёртку, где все записи идут через барьер, чтобы другие потоки ждали, а чтение — через обычный sync. Как очередь в столовую, блядь: один пишет — все стоят и ждут.
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-то обновлять надо на главном, ёпта! Не забудь!
DispatchQueue.main.async {
// Вот тут уже безопасно ткнуть интерфейс
}
}
}
var values: [T] {
queue.sync { array } // Читаем тоже безопасно, а то мало ли
}
}
Вариант второй: Модный Actor (Swift 5.5+, iOS 13+)
Это уже из новых песен, блядь. Актор — он как сторож у массива. Один поток в него зашёл — другие ждут своей очереди. Красиво, нативно, компилятор сам подсказывает, где await вставить.
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()
}
Вариант третий: @MainActor для тех, кто любит, чтобы UI сам обновлялся
Помечаешь весь класс или метод @MainActor — и всё, что в нём происходит, автоматически летит на главный поток. Удобно, когда у тебя ViewModel и надо, чтобы @Published свойства не взрывались.
@MainActor
class ViewModel: ObservableObject {
@Published var items: [String] = []
func fetchData() async {
let newItems = await fetchFromBackground() // Это где-то в фоне
// А вот эта строка уже автоматом на главном, потому что весь класс на @MainActor
items.append(contentsOf: newItems)
}
}
А теперь, блядь, главные правила, которые надо выжечь на подкорке:
- Любое изменение массива из нескольких потоков должно быть синхронизировано. Без вариантов. Иначе — рандомные краши, потерянные данные, и ты будешь искать баг неделю, а он в одной строчке.
- Чтение — тоже операция, ёпта! Если один поток пишет, а другой в этот момент читает — это тоже race condition. Так что читай через тот же механизм синхронизации.
- UI обновлять — только на главном потоке. Сколько уже можно об этом говорить? Даже если данные пришли из фона, перекинь обновление интерфейса в
DispatchQueue.main.async. - Если нужна супер-производительность (миллионы операций в секунду, хотя хрен ты такое упрёшь в мобильном приложении), можешь ковыряться с
NSLockилиos_unfair_lock. Но сначала убедись, что барьер или актор — это реально бутылочное горлышко. - Не изобретай велосипед. Посмотри в сторону готовых решений, типа
Atomicиз пакетаSwift Collections. Может, там уже всё придумали за тебя.
Вот и вся магия, блядь. Главное — не лезь с бульдозером в тонкий механизм, а то получишь ебаный клубок из гонок данных и нестабильного интерфейса. Делай по правилам, и будет тебе счастье.