티스토리 뷰

반응형

 
안녕하세요. 오늘은 가볍게 SwiftUI의 View의 구현부에 명시되어있는 어떤 개념에 대해서 가볍게 보려고 해요. 바로 메인스레드에서의 동작을 보장시켜주는 MainActor에 대해서입니다.

Swift Concurrency, MainActor의 개념과 사용방식, MainActor가 사용되는 케이스와 더불어 async await 메서드를 함께 사용하는 몇가지 케이스에서 메인스레드의 동작유무를 보도록 할게요.
 

 


SwiftUI, View protocol에 정의되어있는 @MainActor

View에 대한 구현부에요. 아래 body를 보시면, @MainActor가 정의되어있는데요.

@MainActor가 정의되어있는 영역 내의 코드는 메인스레드에서의 동작을 보장해요.

다만 그 안에 부분적으로 DispatchQueue.global().async { ... } 나 async 메서드를 사용하면 그부분은 부분적으로 백그라운드 스레드에서 동작으로 하게 됩니다.
 


Swift Concurrency, MainActor가 무엇인가?

MainActor에 대한 설명이에요. main dispatch queue와 동등한 효과를 가져다 주기때문에 DispatchQueue.main.async { ... } 를 사용하는 대신 MainActor를 사용할 수도 있습니다.

단, MainActor는 DispatchQueue와 달리 iOS13+ 에서 사용가능하다는 제약사항이 있습니다.

따라서 View의 body 블럭 내에는 기본적으로 메인스레드에서 동작할 것이란 생각을 해볼 수 있을것 같아요. 그래서 다양한 케이스에서 코드가 메인스레드에서 동작하는지 알아보겠습니다.
 
 

첫번째는 .task { ... } 블럭 내에서의 동작입니다. .task 블럭은 View의 extension 메서드이기때문에 MainActor로 동작하는 View의 메서드로, 메인스레드에서 동작하게 됩니다.

* .task { ... } 블럭은 SwiftUI의 특정 뷰가 onAppear 시점일때 async await 메서드를 호출하고 싶으면 사용 가능합니다. iOS15+에서 사용 가능합니다.

하지만 MainActor로 동작하는 블럭 내에서 부분적으로 DispatchQueue.global().async { ... }, async 메서드 등을 사용하면 내부 동작은 다를 수 있다고 했습니다. 따라서 22, 23행의 async await 방식으로 동작하는 메서드는 내부 구현부를 봐야 할 것 같아요.

일단 27행에 구현된 asyncMethodInView() async { ... } 는 MainActor 등이 명시되어있지 않은 일반 async await 메서드입니다. 이런 경우에는 DispatchQueue.main 블럭 내에서 사용되거나, MainActor 명시가 되어있지 않기 때문에 백그라운드 스레드에서 동작을 할 것으로 볼 수도 있는데요. 과연 그럴까요?

이젠 이어서 viewModel도 보겠습니다.
 
 

MainViewModel은 ObservableObject를 채택하기 때문에 내부에 @Published로 정의된 값이 변경될때 View의 업데이트와 직결되기 때문에 @Published var title: String 멤버의 값을 변경할때에는 반드시 메인스레드에서 동작을 해주어야 합니다. 

따라서 37행, asyncMethod() 메서드 위에 @MainActor를 명시해주었습니다. 이로써 title 값이 변경될때 메인스레드에서 동작을 합니다.

반면 40행, await 키워드 뒤에 사용되는 asyncMethod2()는 내부적으로는 백그라운드 스레드에서 동작을 합니다. 결과적으로, title 값은 "New Hello, world!"로 바뀌고, 그에 따라 View는 업데이트 됩니다.
 


앱 실행 및 코드 출력 결과 확인하기

앱 실행 결과입니다. New Hello, world! 로 변경되어 보여지는 것을 확인가능합니다.

위에 작성했던 코드의 디버깅 출력 결과도 유추가 되시나요? 그 결과는 아래 참고하시면 됩니다.
 
 

대부분 생각대로 나왔을텐데 주목할 부분은 4) 출력 결과입니다. 별도 MainActor 명시가 없었음에도 async await 방식 메서드 실행이 메인스레드에서 동작하는 것입니다.

이는 View 내의 메서드가 암시적으로 MainActor가 명시되어 동작되기 때문입니다.

View 내에 정의한 @StateObject 멤버로 인해 암시적으로 해당 뷰에 MainActor가 적용된다고 해요. (이부분 혹 잘못되었다면 의견주세요.)

오늘은 SwiftUI로 개발을 할때 자주 사용하게 되는 몇가지 케이스에서 @MainActor를 명시하냐 안하냐에 따라 메인스레드에서 동작을 하는지를 가볍게 볼 수 있었습니다.

MainActor를 활용하면 보다 간결하게 특정 영역이 메인스레드에서 동작함을 명시할 수 있었고, View와 같은 protocol은 암시적으로 @MainActor가 명시되어있음도 알아보았습니다.

MainActor가 명시되어있더라도, 내부에 백그라운드 비동기 코드를 작성하면 부분적으로 백그라운드에서 동작할 수 있음도 알 수 있었습니다.

관련해서 피드백이나 의견있으시면 댓글 부탁드립니다. 감사합니다. 🤗

* 참고 레퍼런스

Determining whether an async function will run on the main actor

Hi, A few days ago I saw this Tweet which features the following code: struct ContentView2: View { @State var string = "" @StateObject var vm = ViewModel() var body: some View { VStack { Button { Task { await vm.fetch1() } Task { await vm.fetch2() } Task {

forums.swift.org

 
 

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함