Actor 란?
- Class와 같은 참조타입
- Class의 특징을 모조리 가지고 있음(프로토콜, Extension, 프로퍼티, 이니셜라이저, subscripts 등등..)
- Class와 달리 상속 미지원
- Class와 달리 actor는 한 번에 하나의 작업만 변경 가능한 상태에 접근할 수 있음
클래스와 많이 닮은 녀석이라는 건 알겠다.
그럼 바로 실습해보자
개발자는 고로 코드로 대화해야한다.
class NumClass {
var num: Int = 0
func addOne() {
num += 1
}
}
actor NumActor {
var num: Int = 0
func addOne() {
num += 1
}
}
let numClass = NumClass()
let numActor = NumActor()
DispatchQueue.concurrentPerform(iterations: 10000) { index in
numClass.addOne()
Task {
await numActor.addOne()
}
}
// 대강 1초 후면 모든 작업이 끝나있을 거기 때문에 1초 딜레이
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("NumClass = \(numClass.num)")
Task {
let val = await numActor.num
print("NumActor = \(val)")
}
}
아주 간단하게 실습해보았다.
num이라는 int 프로퍼티를 가지고 있는 class와 actor 각각 선언해주었고
동시에 만번 num에 1씩 더해주었다.
결과는 아래와 같다.
"NumClass = 9973"
"NumActor = 10000"
예상한대로 class는 레이스컨디션 발생으로 불규칙적인 결과가 나왔고
actor는 스레드세이프하였기 때문에 원하던 결과에 도달할 수 있었다.
이러한 NumActor의 num 프로퍼티를 actor-isolated 프로퍼티라고 애플은 칭하고 있다.
actor-isolated 프로퍼티의 단점은 실습한 코드와 같이 async하게 호출해줘야하기 때문에 task 같은걸로 감싸줘야한다.
개발하다보면 분명 Actor로 만든 프로퍼티 중에는 스레드세이프 필요없이 막써도 되는 프로퍼티나 메서드가 필요할 것이다.
그때는 nonisolated 속성을 붙여주면 된다.
actor NumActor {
nonisolated let name = "actor"
var num: Int = 0
func addOne() {
num += 1
}
nonisolated func callMe() -> String {
return name
}
}
let numActor = NumActor()
print(numActor.name) // "actor"
print(numActor.callMe()) // "actor"
nonisolated는 이처럼 sync하게 호출할 수 있다.
thread-safe를 생각하며 코드를 짜지 않아도 기본적으로 강제성을 부여한다는 점에서 Actor는 괜찮은 녀석인 것 같습니다.