How to use MVVM with Async/Await in Swift | Swift 동시성 #14

Sung Daegyu·2024년 5월 1일

Swiftful Thinking

Swift: How to use MVVM with Async/Await in Swift | Swift Concurrency #14

상황 설명.

총 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에서는 위의 ManagerActor에 데이터를 요청하여 받고, 이를 @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()
          }
        }
    }

전체 구조 이미지

키 포인트.

  1. View 내에서는 async를 사용하지 않고, 비동기를 사용하는 경우 ViewModel로 이전한다.

    viewModel.onCallToActionButtonPressed()
    • 뷰에서 쓰인 onCallToActionButtonPressed()는 사실 비동기 함수이다.
    • async/await를 붙여서 비동기로 view 내에 표현하는 것이 아니라, viewModel에서 함수를 정의할 때, 함수 내부를 Task{}로 감싸주었다.
    • 결국, 같은 구현이지만 view내에 복잡성을 줄이고 더욱 단순/갈끔하게 표현하는 효과가 있다.
    • 만약, onCallToActionButtonPressed()를 비동기 함수로 바꾼다면?
      Button(viewModel.myData) {
      	Task{
      		viewModel.onCallToActionButtonPressed()
      	}
      }
      • View에서 Task로 감싸주어야 한다.
      • 더 복잡해진다.
  2. @MainActor의 사용

    • @MainActor는 해당 코드가 main thread에서 실행되어야 하는 경우 붙이는 anotation이다.
    • 즉, 해당 변수/함수/클로져가 View의 UI와 직접적인 연관이 있는 경우 사용한다.
    • @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 {
        	...
        }
        • ViewModel은 보통 View에 가장 밀접하게 연관되어 있는 변수들을 변경한다. (즉, UI와 연관된 변수들이 많다.)
        • 따라서 ViewModel 전체를 @MainActor로 감싸는 것이 제일 간단한 방법.

참고

https://www.youtube.com/watch?v=OpJcInSZpc8&list=PLwvDm4Vfkdphr2Dl4sY4rS9PLzPdyi8PM&index=15

profile
대규의 개발로그

0개의 댓글