[SwiftUI] @globalActor

Junyoung Park·2022년 8월 29일
0

SwiftUI

목록 보기
63/136
post-thumbnail
post-custom-banner

How to use Global Actors in Swift (@globalActor) | Swift Concurrency #10

@globalActor

@globalActor의 정의

  • MainActor의 작동이 메인 스레드를 통해 이루어지는 게 보장(싱글턴)되는 것과 마찬가지로 특정 액터 사용을 글로벌 스레드를 통해 사용할 수 있도록 하는 방법

@globalActor의 사용 방법

  • shared를 사용하는 싱글턴 인스턴스로 사용해야 함
  • @globalActor를 따르는 특정 구조체 (또는 파이널 클래스) 내부에서 특정 액터를 싱글턴으로 구현
  • 데이터 패치를 구독하는 특정 뷰 모델에서 해당 글로벌 액터의 인스턴스 사용 → 데이터 패치는 MainActor에서 실행되어야 함을 주의

핵심 코드

    @MainActor func getDataFromMain() {
        Task {
            let data = await dataServiceGlobal.getDataFromDatabase()
            self.dataArray = data
            // Get data using MainThread (via MainActor)
        }
    }
  • 글로벌 액터를 통해 데이터를 패치한 이후 UI 패치에 사용할 데이터에 데이터를 넣어주는 과정은 메인 액터, 즉 메인 스레드를 통해 실행

소스 코드

import SwiftUI

@globalActor struct GlobalActorBootCampGlobalActor {
    static var shared = GlobalActorBootCampActor()
}

@globalActor final class GlobalActorBootCampGlobalActorFinalClass {
    static var shared = GlobalActorBootCampActor()
}

actor GlobalActorBootCampActor {

    func getDataFromDatabase() -> [String] {
        return ["MockData1", "MockData2", "MockData3"]
    }
    nonisolated func getDataFromDatabaseNonisolated() -> [String] {
        return ["MockData1", "MockData2", "MockData3"]
    }
}
  • 비동기적 데이터 패치 함수를 사용하는 GlobalActorBootCampActor
  • 액터의 actor-isolated를 보장하지 않아도 된다는 키워드 nonisolated를 사용한다면 await를 하지 않아도 곧바로 사용 가능
  • 해당 액터를 글로벌 스레드에서 실행하기 위한 @globalActor은 싱글턴으로 선언해야 함
// @MainActor class GlobalActorBootCampViewModel: ObservableObject {
// -> Entire variables to be run inside MainActor using @MainActor
class GlobalActorBootCampViewModel: ObservableObject {
    @Published var dataArray: [String] = []
    @MainActor @Published var dataArrayUsingMain: [String] = []
    let dataService = GlobalActorBootCampActor()
    let dataServiceGlobal = GlobalActorBootCampGlobalActor.shared
    func getData() async {
        // Heavy and Complex Methods ->
        // MainActor (using MainThread) <-> GlobalActor (using GlobalThread)
        // GlobalActor: shared(Singleton)
        let data = await dataService.getDataFromDatabase()
        let data2 = dataService.getDataFromDatabaseNonisolated()
        self.dataArray = data
    }
    @GlobalActorBootCampGlobalActor func getDataFromGlobal() {
        Task {
            let data = await dataServiceGlobal.getDataFromDatabase()
            await MainActor.run(body: {
                self.dataArray = data
            })
        }
    }
    @MainActor func getDataFromMain() {
        Task {
            let data = await dataServiceGlobal.getDataFromDatabase()
            self.dataArray = data
            // Get data using MainThread (via MainActor)
        }
    }
    @GlobalActorBootCampGlobalActor func getDataFromGlobal2() {
        Task {
            let data = await dataServiceGlobal.getDataFromDatabase()
            // self.dataArrayUsingMain = data -> dataArrayUsingMain <- MainAcor needed
            // Property 'dataArrayUsingMain' isolated to global actor 'MainActor' can not be mutated from different global actor 'GlobalActorBootCampGlobalActor'
            await MainActor.run(body: {
                self.dataArrayUsingMain = data
            })
        }
    }
}
  • 글로벌 액터로 선언한 dataServiceGlobal은 해당 글로벌 액터가 가리키고 있는 싱글턴 객체
  • 해당 데이터 서비스에서 데이터를 패치할 때 @MainActor, @GlobalActorBootCampGlobalActor 등 특정 액터를 사용한다는 것을 알려줌 → 스레드 종류 변경 가능 → UI 업데이트는 메인 스레드에서 이뤄져야 하기 때문에 await MainActor가 글로벌 액터를 통해 데이터를 패치하는 코드 블럭 내부에서 선언되어야 함
  • UI 리렌더링에 사용할 특정 데이터를 @MainActor로 선언한다면 보다 안전하게 글로벌 액터를 통한 데이터 패치 내부 코드 블럭에서 사용 가능(컴파일러를 통해 해당 데이터 패치가 메인 스레드, 즉 메인 액터를 통해 실행되어야 함을 사전에 알 수 있음)
  • 해당 ObservableObject 뷰 모델 자체가 MainActor로 선언할 수도 있음
  • 글로벌 액터 싱글턴을 구현하는 곳은 구조체 또는 파이널 클래스: 여러 곳에서 참조하지 못하고(값 타입) 상속하지 못함(파이널)
struct GlobalActorBootCamp: View {
    @StateObject private var viewModel = GlobalActorBootCampViewModel()
    var body: some View {
        ScrollView {
            VStack {
                ForEach(viewModel.dataArray, id:\.self) { data in
                    Text(data)
                        .font(.headline)
                        .fontWeight(.semibold)
                }
            }
        }
        .task {
//            await viewModel.getDataFromGlobal()
            // @GlobalActor -> even though not "async" keyword, it has to wait for those funcs to come back using "await"
            viewModel.getDataFromMain()
        }
    }
}
  • task 코드 블럭 내부에서 뷰 모델이 사용하는 데이터 패치는 메인 액터 또는 글로벌 액터를 통해 수행
profile
JUST DO IT
post-custom-banner

0개의 댓글