How to use Global Actors in Swift (@globalActor) | Swift Concurrency #10
MainActor
의 작동이 메인 스레드를 통해 이루어지는 게 보장(싱글턴)되는 것과 마찬가지로 특정 액터 사용을 글로벌 스레드를 통해 사용할 수 있도록 하는 방법shared
를 사용하는 싱글턴 인스턴스로 사용해야 함@globalActor
를 따르는 특정 구조체 (또는 파이널 클래스) 내부에서 특정 액터를 싱글턴으로 구현MainActor
에서 실행되어야 함을 주의 @MainActor func getDataFromMain() {
Task {
let data = await dataServiceGlobal.getDataFromDatabase()
self.dataArray = data
// Get data using MainThread (via MainActor)
}
}
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
가 글로벌 액터를 통해 데이터를 패치하는 코드 블럭 내부에서 선언되어야 함@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
코드 블럭 내부에서 뷰 모델이 사용하는 데이터 패치는 메인 액터 또는 글로벌 액터를 통해 수행