Безопасно ли наполнять массив данными, полученными асинхронно через async/await?

Ответ

Да, безопасно, при условии корректной обработки асинхронности и потоков. Ключевые моменты:

  1. Избегание гонок данных (Data Races): Если массив является изменяемой (var) переменной, и к ней могут параллельно обращаться несколько задач, необходим механизм синхронизации. В Swift для этого используют Actor.

    actor DataCollector {
        private var results = [Int]()
    
        func append(_ value: Int) {
            results.append(value)
        }
    
        func getResults() -> [Int] {
            return results
        }
    }
  2. Контроль порядка элементов:

    • Последовательно: Используйте await в цикле, если порядок важен.
      var orderedResults = [Int]()
      for id in ids {
          let data = await fetchData(for: id) // Ждем каждый результат
          orderedResults.append(data)
      }
    • Параллельно: Используйте TaskGroup, если порядок не важен и нужна производительность.
      let parallelResults = await withTaskGroup(of: Int.self) { group in
          for id in ids {
              group.addTask { await fetchData(for: id) }
          }
          // Собираем результаты по мере готовности
          return await group.reduce(into: []) { $0.append($1) }
      }
  3. Работа с UI: Все операции, обновляющие UI, должны выполняться на главном потоке. Используйте @MainActor.

    @MainActor
    func updateUI(with newData: [Int]) {
        self.dataSource = newData
        tableView.reloadData()
    }

Ответ 18+ 🔞

А, ну это ж классика, блядь! Ты про то, когда накидываешь кучу асинхронных задач, а потом пытаешься всё это добро в один массив запихнуть, да?

Так вот, слушай сюда, мудила. Да, безопасно, но только если ты не распиздяй. Если будешь лепить как попало — получишь гонку данных, и твой массив превратится в тыкву, в которую насрали с разных сторон одновременно. Пиздец наступит тихий и незаметный.

Главное — это синхронизация, ёпта! Если у тебя var массив и туда ломятся несколько потоков или тасков, то это как в общественный туалет без дверей — все всё видят, и всем неловко, а в итоге всё засрано. Нужен Actor. Это такой сторож с дубиной, который пускает по одному. Смотри, как это выглядит:

actor DataCollector {
    private var results = [Int]()

    func append(_ value: Int) {
        results.append(value)
    }

    func getResults() -> [Int] {
        return results
    }
}

Видишь? private var results. К этой приватной переменной с улицы не подойдёшь. Хочешь добавить что-то? Иди через метод append, и сторож-актор тебя пропустит одного. Всё чинно-благородно, без давки.

А теперь про порядок, потому что это отдельная песня, блядь.

Если тебе важен порядок, как в очереди за колбасой в девяностые, то жди каждый результат по очереди. Терпения ебать ноль, но что поделать.

var orderedResults = [Int]()
for id in ids {
    let data = await fetchData(for: id) // Сидишь и ждёшь, пока передний хуй с колбасой отвалится
    orderedResults.append(data)
}

А если тебе похуй на порядок, и главное — скорость, чтобы быстрее всех сбегать, то это TaskGroup. Запускаешь всё разом, а потом собираешь, что прибежало.

let parallelResults = await withTaskGroup(of: Int.self) { group in
    for id in ids {
        group.addTask { await fetchData(for: id) }
    }
    // Собираем результаты по мере готовности, как грибы после дождя
    return await group.reduce(into: []) { $0.append($1) }
}

И последнее, самое важное, блядь! Работа с UI. Если ты с этими массивами полезешь обновлять таблицу или лейблы не с того потока — получишь краш, и будешь сидеть, чесать репу. Все UI-шные дела должны быть на главном потоке. Для этого есть @MainActor.

@MainActor
func updateUI(with newData: [Int]) {
    self.dataSource = newData // Вот тут уже безопасно, потому что главный поток один
    tableView.reloadData()
}

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