Swift에서 AsyncSequence로 데이터를 단계적으로 불러오기
Iterative data loading in Swift
핵심 요약
- CardioBot의 화면은 HealthKit 요청 40~50개를 사용해 여러 건강 지표를 구성하며, 요청을 한꺼번에 시작하면 모든 결과가 모일 때까지 화면 갱신을 기다려야 한다.
- MetricsSequence는 데이터 항목별 로딩 단계를 AsyncSequence로 표현하고, 각 단계에서 MetricsSnapshot을 갱신해 누적된 부분 결과를 반환한다.
- for await 루프는 매 단계의 스냅샷으로 화면 상태를 업데이트하며, 로딩 순서를 조정하거나 AsyncSequence의 debounce·map 같은 연산자를 적용할 수 있다.
도입
CardioBot의 오늘 화면은 활동, 수면, 운동, 회복, 활력 징후 등 최근 건강 데이터를 표시하기 위해 평균 40~50개의 HealthKit 요청을 수행한다. 이를 모두 async let으로 시작해도 Cooperative Thread Pool의 동시 실행 수는 제한되지만, 많은 비동기 작업을 만들고 모든 결과가 준비될 때까지 기다리면 화면 갱신이 늦어진다. 글은 로딩을 단계별로 나누는 AsyncSequence 구조를 제시한다.
상세 요약
한꺼번에 시작하는 요청의 한계
CardioBot의 오늘 화면은 최근 건강 데이터를 구성하기 위해 평균 40~50개의 HealthKit 요청을 수행한다. 예시에서는 심박 포인트, 심폐 체력, 운동, HRV, 안정 시 심박수, 수면 등의 결과를 MetricsSnapshot에 모은다.
모든 요청을 async let으로 선언해도 50개 작업이 동시에 실행되는 것은 아니다. Swift의 Cooperative Thread Pool은 스레드 수를 보통 CPU 코어 수에 제한해 스레드가 과도하게 늘어나는 것을 막고, 동시 작업 수도 제한한다. 다만 많은 비동기 작업을 만드는 데 메모리가 들고, 화면을 갱신하려면 모든 작업의 완료를 기다려야 할 수 있다.
로딩 단계를 AsyncSequence로 표현
대안은 데이터 로딩을 AsyncSequence로 모델링하는 것이다. 글의 MetricsSequence는 HealthService와 조회 구간을 보유하고, makeAsyncIterator에서 AsyncIterator를 반환한다. Iterator 안의 Step 열거형은 heartPoints, workouts, cardioFitness, sleep, hrv, restingHeartRate 등 로딩 순서를 정의한다. CaseIterable을 채택해 모든 단계의 순회자를 만든다.
next()는 현재 단계에 해당하는 비동기 함수를 호출하고, 반환된 데이터를 MetricsSnapshot에 반영한다. 그런 다음 누적된 스냅샷을 반환한다. 따라서 한 번의 전체 결과 대신 단계별 부분 결과를 연속해서 전달한다. 예시에서는 누적 상태를 Sequence 내부에 보관하지만, 부분 스냅샷을 반환하고 기능 쪽에서 누적하는 방식도 가능하다고 설명한다. 각 helper 함수 안에는 async let을 사용할 수 있다.
취소와 종료 처리
next()는 단계 처리 후 Task.isCancelled를 확인한다. 작업이 취소된 상태라면 nil을 반환한다. AsyncIterator에서 nil은 더 이상 반환할 요소가 없다는 뜻이므로, 남은 단계가 없을 때도 nil을 반환해 시퀀스를 끝낸다.
단계마다 화면 상태 갱신
뷰 모델은 MetricsSequence를 for await로 순회하고, 매 반복에서 받은 스냅샷을 metrics 상태에 대입한다. 이 구조에서는 전체 데이터 로딩이 끝날 때까지 기다리는 대신 각 단계의 결과를 화면 상태에 반영한다. 로딩 단계의 순서를 조정하거나, 현재 사용자에게 보이는 섹션의 데이터부터 불러오도록 시퀀스를 구성할 수도 있다.
AsyncSequence를 사용하면 debounce, map 등의 연산자도 적용할 수 있다. 글의 핵심은 요청 수만 줄이는 데 있지 않다. 데이터를 어떤 순서로 전달하고 각 결과를 언제 화면에 반영할지 제어해, 데이터가 많은 화면을 단계적으로 갱신하는 것이다.
결론
MetricsSequence는 정해진 순서로 HealthKit 데이터를 불러오고, 매번 누적된 MetricsSnapshot을 반환한다. 뷰 모델은 for await 루프에서 각 스냅샷을 상태에 반영하므로 전체 요청이 끝나기 전에도 단계별 결과를 화면에 전달한다. 로딩 단계를 보이는 섹션에 맞춰 조정할 수 있고, AsyncSequence의 debounce나 map 연산자도 사용할 수 있다.