Actor
Actor를 알아보기 전에 아래 멀티 스레드 환경에서는 하나의 여러개의 스레드들이 하나의 Heap 메모리를 공유해서 사용하고 있는 것을 확인할 수 있습니다. Heap 메모리는 Actor와 Class 가 저장되며 Actor와 Class의 주요 차이점은 Actor는 Thread Safe 하다는 것입니다.
Actor 사용하는 이유
Actor는 앞서 얘기한것처럼 Thread Safe 합니다. 설명은 간단합니다, 여러 스레드들이 하나의 Heap 메모리에 동시에 접근하게 되면 Data Race가 발생할 수 있고 심하면 앱이 crash 되어 불능상태가 될 수 있는데 Thread Safe는 이처럼 여러 스레드들이 하나의 Heap 메모리에 동시 접근을 못하도록 방지해주는 것입니다.
Actor 선언하는 방법
선언하는 방법은 매우 간단합니다, 클래스처럼 앞에 actor를 작성하면 끝입니다
actor MyActor {
var title: String
init(title: String) {
self.title = title
}
func updateTitle(newTitle: String) {
title = newTitle
}
}
이후 MyActor 인스턴스를 만들어 프로퍼티의 값을 print하고 인스턴스를 복사하고 수정하는 코드를 작성해보겠습니다.
private func actorTest1() {
let objectA = MyActor(title: "Starting title!")
print("ObjectA: ", objectA.title)
let objectB = objectA
print("ObjectB: ", objectA.title)
objectB.title = "Second title"
print("ObjectB title changed.")
print("ObjectA: ", objectA.title)
print("ObjectB: ", objectB.title)
}
하지만 에러가 발생하는 것을 확인할 수 있습니다, 이것은 비동기 작업 환경이 아니기 때문에 발생하는 에러로 async를 지원하는 메서드로 만듭니다.
다음으로 에러가 또 발생하는데 이것은 Actor 인스턴스의 데이터를 접근하기 위해서는 Actor는 Thread Safe 타입으로 동시 접근이 불가능해야 하기 때문에 await 키워드를 사용하여 현재 동작이 수행되기 전까지 기다려야 하는 것을 명시해줍니다. 추가로 Task를 사용하여 async 키워드를 없애줍시다.
그리고 actor의 프로퍼티 값은 actor가 Thread Safe 타입이기 때문에 actor 밖에서 변경될 수 없습니다. 그렇기 때문에 actor 프로퍼티 값을 변경하기 위해서는 actor에 메서드를 만들어 변경해야합니다.
private func actorTest1() {
Task {
let objectA = MyActor(title: "Starting title!")
await print("ObjectA: ", objectA.title)
let objectB = objectA
await print("ObjectB: ", objectA.title)
await objectB.updateTitle(newTitle: "Second title")
print("ObjectB title changed.")
await print("ObjectA: ", objectA.title)
await print("ObjectB: ", objectB.title)
}
}
Actor의 프로퍼티 접근할 때 (nonisolated)
Actor의 메서드 실행 뿐만 아니라 프로퍼티에 접근하기 위해서는 await 키워드를 사용하여 값을 사용해야 한다는 특징이 있습니다.
이것 또한 Thread Safe와 연관되어 있으며 data race가 발생하지 않기 위함입니다.
await print("ObjectB: ", objectA.title)
만약 Actor의 프로퍼티가 Thread Safe를 고려하지 않아도 되는 상황이 있을 수 있습니다, 이런 상황에서는 굳지 작업을 수행하기 위해서 await 키워드를 사용하지 않아도 될 것입니다, 이런 상황에서 nonisolated를 사용합니다. 다음 Actor를 살펴보겠습니다.
nonisolated: 고립되지 않았다는 의미로 데이터가 actor의 thread safe 특성의 영향을 받지 않는다고 생각하시면 됩니다.
isolated: 고립되었다는 의미로 데이터가 thread safe 특성을 지킨다고 생각하시면 됩니다.
actor MyActorDataManager {
static let instance = MyActorDataManager()
private init() { }
var data: [String] = []
func getRandomData() -> String? {
self.data.append(UUID().uuidString)
print(Thread.current)
return self.data.randomElement()
}
nonisolated func getSavedData() -> String {
return "NEW DATA"
}
}
다음은 Actor 싱글톤을 사용하는 코드입니다.
struct HomeView: View {
let manager = MyActorDataManager.instance
var body: some View {
Text(text)
.onAppear {
let newString = manager.getSavedData()
let newString2 = await manager.getRandomData()
}
확인해보면 nonisolated 로 선언된 메서드는 동시 접근을 신경쓰지 않기 때문에 await 키워드를 사용하지 않아도 됩니다.
하지만 nonisolated 로 선언되지 않은 메서드는 Thread Safe 해야 하므로 await을 사용하여 data race를 방지합니다.
만약 nonisolated가 선언되지 않았다면 다음처럼 구현되어야 합니다.
Text(text)
.font(.headline)
.onAppear {
let newString = manager.getSavedData()
Task {
let data = await manager.getRandomData()
}
}
'SwiftUI' 카테고리의 다른 글
MacPaw/OpenAI SwiftUI Package 분석하기 (0) | 2024.05.25 |
---|---|
Global Actor과 Main Actor (0) | 2024.05.24 |
Value Type, Reference Type, Stack, Heap, Struct, Class 사용되는 상황들, @StateObject 가 클래스여야 하는 이유 (0) | 2024.05.20 |
Class, ARC, Weak Self (0) | 2024.05.20 |
Struct 구조체 (0) | 2024.05.18 |