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

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

Ответ

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

  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()
    }