How to use AsyncPublisher to convert @Published to Async / Await | Swift Concurrency #12
value
프로퍼티를 관찰하는 방법for in
구문을 통해 해당 퍼블리셔가 리턴하는 데이터 타입의 특정 값을 관찰 → 해당 값을 바인딩 private func addSubscriber2() {
Task {
for await value in dataService.$dataArray.values {
// Executed Async
await MainActor.run(body: {
self.dataArray = value
})
}
}
}
dataService
의 $dataArray
를 구독하던 것과 달리 $dataArray
의 값을 직접 관찰 가능 → Combine
프레임워크의 sink
등 구독 추가 방법과는 또 다른 방법으로 특정 값의 변화를 감지 가능import SwiftUI
import Combine
class AsyncPublisherBootCampDataService {
@Published var dataArray: [String] = []
func getData() async {
dataArray.append("MockData1")
try? await Task.sleep(nanoseconds: 2_000_000_000)
dataArray.append("MockData2")
try? await Task.sleep(nanoseconds: 2_000_000_000)
dataArray.append("MockData3")
try? await Task.sleep(nanoseconds: 2_000_000_000)
dataArray.append("MockData4")
}
}
class AsyncPublisherBootCampViewModel: ObservableObject {
@MainActor @Published var dataArray: [String] = []
let dataService = AsyncPublisherBootCampDataService()
var cancellables = Set<AnyCancellable>()
init() {
addSubscriber3()
}
private func addSubsriber() {
dataService.$dataArray
.receive(on: DispatchQueue.main)
.sink { [weak self] dataArray in
guard let self = self else { return }
DispatchQueue.main.async {
self.dataArray = dataArray
}
}
.store(in: &cancellables)
}
private func addSubscriber2() {
Task {
for await value in dataService.$dataArray.values {
// Executed Async
await MainActor.run(body: {
self.dataArray = value
})
}
}
}
private func addSubscriber3() {
Task {
await MainActor.run(body: {
self.dataArray = ["One"]
})
for await value in dataService.$dataArray.values {
// Executed Async
await MainActor.run(body: {
self.dataArray = value
})
break
// using break -> escape those for-ever waiting loop
}
await MainActor.run(body: {
self.dataArray = ["Two"]
})
}
}
func getData() async {
await dataService.getData()
}
}
Combine
프레임워크의 구독 추가 방식을 사용하고 있는 addSubscriber
함수values
를 통해 AsyncPublisher
사용하고 있는 addSubscriber2
함수for await value in...
문을 브레이크를 통해 탈출해야 함 → 해당 구독자는 해당 퍼블리셔가 언제 끝나는지 알 수 없기 때문에 별도의 추가적인 탈출 방법이 필요struct AsyncPublisherBootCamp: View {
@StateObject private var viewModel = AsyncPublisherBootCampViewModel()
var body: some View {
ScrollView {
VStack {
ForEach(viewModel.dataArray, id:\.self) { data in
// Executed immediately
Text(data)
.font(.headline)
.fontWeight(.semibold)
}
}
}
.task {
await viewModel.getData()
}
}
}
viewModel
의 getData
함수를 통해 데이터 서비스 클래스의 데이터 패치가 비동기적으로 실행, 해당 퍼블리셔의 데이터 추가에 따라 (기존 뷰 모델이 이니셜라이즈될 때 해당 데이터 서비스를 구독하고 있기 때문에) 데이터 패치에 따른 뷰 모델의 관찰이 실시간 연동 가능