KoreaMango 나무

[iOS App Dev Tutorials] SwiftUI - Persistence and Concurrency (3) 본문

iOS/iOS App Dev Tutorials

[iOS App Dev Tutorials] SwiftUI - Persistence and Concurrency (3)

KoreaMango 2022. 5. 18. 21:26
 

Apple Developer Documentation

 

developer.apple.com

3. Adopting Swift Concurrency

Swift 5.5 에는 성능 좋은 비동기 코드를 더 쉽게 작성할 수 있는 새로운 비동기와 대기 키워드가 포함되어 있다.

비동기 함수를 정의하고 호출하는 방법을 배우고, 구조화된 동시성이 복잡한 비동기 함수를 단순화하는 방법을 살펴보자!

Simplifying Asynchronous Code

static func load(completion: @escaping (Result<[DailyScrum], Error>)->Void) {
  DispatchQueue.global(qos: .background).async {
      do {
          // Decode scrums
          DispatchQueue.main.async {
              completion(.success(dailyScrums))
          }
      } catch {
          DispatchQueue.main.async {
              completion(.failure(error))
          }
      }
  }
}

우선 UI가 응답할 수 있도록 이 기능은 백그라운드에 스크럼 데이터를 디코딩한다.

그런 다음 컴플리션 클로져를 사용해서 백그라운드에서 작업이 완료되면 메인 큐의 저장소에 업데이트되게 한다.

이러한 콜백 기반의 비동기 코드는 스위프트에서 흔하다. 하지만 읽기엔 어렵다.. 특히 컴플리션 핸들러를 서로 중첩하는 경우 어렵다.

다행히 Swift 5.5는 새로운 비동기, 대기 패턴을 도입해서 일반적인 동기 코드에 가까운 비동기 함수를 작성할 수 있다.

다음으로 비동기 및 대기 기능을 사용해서 비동기 함수를 정의하고 호출하는 방법에 대해 배우고, 콜백 기반 비동기 함수를 Swift의 새로운 동시성 기능과 통합하는 방법에 대해 알아보자.

Defining and Asynchronous Function

비동기 함수를 정의하려면 매개변수 목록 뒤에 async 키워드를 추가하고, 함수가 값을 반환하는 경우 반환 화살표(→) 앞에 앞에 추가한다. 아래 예제는 viewModel에는 참가자 배열을 반환하는 fetchParticipants()라는 비동기 함수가 있다.

class ViewModel: ObservableObject {
   @Published var participants: [Participant] = []

   func fetchParticipants() async -> [Participant] {...}
}

Calling an Asynchronous Function

await 키워드를 사용해서 비동기 함수를 호출할 수 있다. 대기 중인 코드는 실행을 일시 중단할 수 있기 때문에 다른 비동기 함수의 본문 내부와 같은 Asynchronous contexts 에서만 await를 사용할 수 있습니다.

아래 예제에서 viewModel은 fetchParticipants()를 호출하는 refresh()라는 새로운 비동기 함수를 포함한다.

class ViewModel: ObservableObject {
   @Published var participants: [Participant] = []

   func refresh() async {
      let fetchedParticipants = await fetchParticipants()
      self.participants = fetchedParticipants
   }
   func fetchParticipants() async -> [Participant] {...}
}

await 키워드를 사용하면 fetchParticipants() 가 완료될 때 까지 시스템이 나머지 함수의 실행을 일시 중지할 수 있다. 기능이 일시 중지되는 동안 스레드는 다른 작업을 수행할 수 있다. fetchParticipants()가 완료되면 시스템은 refresh() 함수의 다음 행을 실행한다. 비동기/ 대기를 사용하면 함수가 선형으로 실행되므로 실행 순서를 더 쉽게 이해할 수 있다.

태스크를 생성해서 동기 컨텍스트에서 비동기 함수를 호출할 수 있다. 아래 예제에서 contentView의 버튼을 사용해서 refresh() 를 call 해보자.

struct ContentView: View {
   @StateObject var model = ViewModel()
 
   var body: some View {
      NavigationView {
         List {
            Button {
               Task {
                  await model.refresh()
               }
            } label: {
               Text("Load Participants")
            }
            ForEach(model.participants) { participant in
               ...
            }
         }
      }
   }
}

SwiftUI는 View가 나타날 때 비동기 함수를 실행할 수 있는 작업 한정자를 제공한다.

struct ContentView: View {
   @StateObject var model = ViewModel()
 
   var body: some View {
      NavigationView {
         List {
            ForEach(model.participants) { participant in
               ...
            }
         }
         .task {
            await model.refresh()
         }
      }
   }
}

.task 를 사용해서 view가 나타날 때 비동기 함수를 실행한다.