[내일배움캠프 9주차 (02/27)]

yeseul jang·2026년 2월 27일

내일배움캠프

목록 보기
14/32

📁 Main 스레드에서 호출하도록 보장하기

⛓️‍💥 초기 코드

func fetchMovieData() {
    Task {
        do {
            let now = try await networkingService.fetchMovies(type: .NowPlaying)
            let up  = try await networkingService.fetchMovies(type: .upcoming)
            let pop = try await networkingService.fetchMovies(type: .popular)

            self.nowPlaying = now
            self.upcoming = up
            self.popular = pop

            self.onUpdate?()
        } catch {
            print(error)
        }
    }
}
  • viewModel에서 URLSession을 사용해서 UI를 그리는데 필요한 데이터를 받아오는 함수이다.
  • networkingService.fetchMovies는 비동기 함수
  • self.onUpdate?(): 이 함수는 UI를 업데이트하는 함수라서 main thread를 보장해야 한다.

⚠️ 주의점

  • 네트워크 요청(fetchMovies)은 비동기라서, 완료 시점이 메인 스레드가 아닐 수 가 있었다.
  • onUpdate에서 VCreloadData()를 하고, onError에서 Alret을 띄울기 예정이기 때문에 무조건 메인에서 해야 안전한 상황이었다.
  • 단순Task으로는 메인 보장을 확신할 수 없다.
  • await를 3번 순차적으로 기다리기 때문에 상대적으로 느렸다.

♻️리팩토링 1️⃣: MainActor.run 사용

func fetchMovieData() {
    Task {
        do {
            async let NowPlaying = networkingService.fetchMovies(type: .nowPlaying)
            async let upcoming = networkingService.fetchMovies(type: .upcoming)
            async let popular = networkingService.fetchMovies(type: .popular)
            
            let (now, up, pop) = try await (NowPlaying, upcoming, popular)
            
            self.nowPlaying = now
            self.upcoming = up
            self.popular = pop

            await MainActor.run { self.onUpdate?() }
        } catch {
            await MainActor.run { self.onErrorMessage?("...") }
        }
    }
}

💡 개선점

  • onUpdate / onErrorMessage?("...")가 항상 메인에서 실행됨(VC에서 안전하게 실행)
  • async let로 요청들을 동시에 시작해서 가장 오래 걸리는 요청 기준으로 끝나도록 속도개선

⚠️ 주의점

  • 호출하는 쪽에서는 그냥 단순 함수처럼 호출되기 때문에 흐름제어가 어려움
  • 코드의 의도가 불분명함(UI와 관련된 부분은 self.onUpdate?()self.onErrorMessage?("...")뿐이라고 읽힐수 있음 )

♻️리팩토링 2️⃣: @MainActor 사용

func fetchMovieData() async {
    do {
        async let nowPlaying = networkingService.fetchMovies(type: .NowPlaying)
        async let upcoming   = networkingService.fetchMovies(type: .upcoming)
        async let popular    = networkingService.fetchMovies(type: .popular)

        let (now, up, pop) = try await (nowPlaying, upcoming, popular)

        self.nowPlaying = now
        self.upcoming = up
        self.popular = pop

        self.onUpdate?()
    } catch {
        self.onErrorMessage?(makeErrorMessage(error))
    }
}

💡 개선점

  • 메인 액터가 함수 전체를 보장해서popular 같은 파라미터의 상태 업데이트와 self.onUpdate?()같은 콜백 호출이 한 군데(MainActor)에서 처리되어서 안전함.
  • 어차피 let (now, up, pop) = try await (nowPlaying, upcoming, popular)에 해당하는 네트워크 통신은 저절로 백그라운드 실행되고 결과 받는 시점에 다시 MainActor로 복귀하기 때문에 상관없음
  • VCreloadData()Alret처리 같은 UI만 맡을 수 있음
  • VCTask { await viewModel.fetchMovieData() }로 호출만 담당하도록 분리

⚠️ 주의점

  • @MainActor에서 무거운 CPU 작업(대량 정렬이나 복잡한 가공 등)을 오래 하면 UI 버벅일 수 있기 때문에 지금처럼 단순 배열 대입이나 콜백 정도일 경우만 이런식으로 진행하는 것이 좋겠다.
profile
iOS 개발

0개의 댓글