클래스와 달리 Actor는 한 번에 하나의 작업만 변경 가능한 상태에 액세스할 수 있도록 허용
하므로 여러 작업의 코드가 동일한 Actor 인스턴스와 상호 작용하는 것이 안전하다.
예시 : 비동기로 값을 수정하고 읽어야 하는 상황
import Foundation
class User {
let id: String
let name: String
init(id: String, name: String) {
self.id = id
self.name = name
}
}
class UserStorage {
private var store = [String: User]()
func store(_ id: String) -> User? {
return store[id]
}
func save(_ user: User) {
self.store[user.id] = user
}
}
let storage = UserStorage()
DispatchQueue.global().async {
storage.save(User(id: "kim", name: "groot"))
}
DispatchQueue.global().async {
print(storage.store("kim")?.name)
}
위의 코드를 실행했을 때 nil을 출력.
save, store 함수는 비동기적으로 실행되기 때문에 store를 실행하는 시점에는 어떤 User도 없는 경우가 있다.
Read와 Update가 동시에 store로 접근하게 되면 런타임 오류가 생길 수 있다.
이 코드를 정상적으로 수행하기 위한 방법
DispatchSemaphore를 사용하는 방법
let storage = UserStorage()
let semaphore = DispatchSemaphore(value: 1)
DispatchQueue.global().async {
semaphore.wait()
storage.save(User(id: "kim", name: "groot"))
semaphore.signal()
}
DispatchQueue.global().async {
semaphore.wait()
print(storage.store("kim")?.name)
semaphore.signal()
}
이렇게 수정하면 정상적으로 값을 저장하고 읽어올 수 있지만, Actor를 사용하면 Swift에서 Race Condition 문제를 처리해준다. Thread Safe한 상태로 선언이 가능하다.
actor UserStorage {
private var store = [String: User]()
func get(_ id: String) -> User? {
return store[id]
}
func save(_ user: User) {
self.store[user.id] = user
}
}
let storage = UserStorage()
Task {
await storage.save(User(id: "kim", name: "groot"))
}
Task {
print(await storage.get("kim")?.name)
}
Actor는 메서드 실행 시 기본적으로 await 키워드를 사용한다.
await로 실행해주게 되면 다음 await는 이전에 실행된 await가 끝나고 나서 다음 작업이 가능하기 때문에 동시에 특정 값에 접근하는 문제가 생기지 않는다. Thread Safe하다
https://www.youtube.com/watch?v=5LKbL-I-CYY
https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html