총 4개의 구조가 있다.
MyManagerClass)final class MyManagerClass {
func getData() async throws -> String {
return "Some Data"
}
}getData()는 비동기로 문자열 데이터를 return 한다.MVVMBootcampViewModel)final class MVVMBootcampViewModel: ObservableObject {
let managerClass = MyManagerClass()
let managerActor = MyManagerActor()
@Published private(set) var myData: String = "Starting text"
private var tasks: [Task<Void, any Error>] = []
func onCallToActionButtonPressed() {
let task1 = Task {
// managerClass와 managerActor에서 데이터를 받아온다.
myData = try await managerClass.getData()
myData = try await managerActor.getData()
}
tasks.append(task1)
}
func cancelAllTasks() {
tasks.forEach { task in
task.cancel()
}
tasks = []
}
}ViewModel에서는 위의 Manager와 Actor에 데이터를 요청하여 받고, 이를 @Published 변수 myData에 반영하여, View의 UI를 업데이트 한다.MVVMBootcamp)struct MVVMBootcamp: View {
// View는 ViewModel과 직접적인 관계를 가진다.
@StateObject var viewModel = MVVMBootcampViewModel()
var body: some View {
VStack {
// ViewModel의 @published var myData 변수가 Button의 제목을 결정한다.
Button(viewModel.myData) {
viewModel.onCallToActionButtonPressed()
}
}
.onDisappear {
viewModel.cancelAllTasks()
}
}
}전체 구조 이미지

View 내에서는 async를 사용하지 않고, 비동기를 사용하는 경우 ViewModel로 이전한다.
viewModel.onCallToActionButtonPressed()
onCallToActionButtonPressed()는 사실 비동기 함수이다.async/await를 붙여서 비동기로 view 내에 표현하는 것이 아니라, viewModel에서 함수를 정의할 때, 함수 내부를 Task{}로 감싸주었다.onCallToActionButtonPressed()를 비동기 함수로 바꾼다면?Button(viewModel.myData) {
Task{
viewModel.onCallToActionButtonPressed()
}
}Task로 감싸주어야 한다.@MainActor의 사용
let task1 = Task {
// 특정 클로져를 @MainActor로 표시할 수 있다.
@MainActor in
myData = try await managerClass.getData()
}@MainActor
func onCallToActionButtonPressed() {
let task1 = Task {
myData = try await managerClass.getData()
}
tasks.append(task1)
}// ViewModel은 보통 View에 가장 밀접하게 연관되어 있는 변수들을 변경한다. (즉, UI와 연관된 변수들이 많다)
// 따라서 ViewModel 전체를 @MainActor로 감싸는 것도 방법이다.
@MainActor
final class MVVMBootcampViewModel: ObservableObject {
...
}https://www.youtube.com/watch?v=OpJcInSZpc8&list=PLwvDm4Vfkdphr2Dl4sY4rS9PLzPdyi8PM&index=15