Why This Work Exists
Actor 1편에서 “왜 actor가 필요한가”를 정리했다면, 2편에서는 “actor 내부와 외부에서 접근 규칙이 왜 다르게 보이는가”를 정리해야 한다. 특히 actor-isolated, cross-actor reference, `await` 필요 여부가 가장 많이 헷갈린다.
ZeddiOS의 Swift ) Actor (2) 글을 바탕으로, actor-isolated 멤버가 무엇인지, 같은 actor 내부 접근과 외부 접근이 어떻게 다른지, 그리고 왜 다른 actor에는 보통 `await`로 접근해야 하는지 정리한다.
2026-04-09
Actor 1편에서 “왜 actor가 필요한가”를 정리했다면, 2편에서는 “actor 내부와 외부에서 접근 규칙이 왜 다르게 보이는가”를 정리해야 한다. 특히 actor-isolated, cross-actor reference, `await` 필요 여부가 가장 많이 헷갈린다.
흔한 오해는 “actor 안의 값은 외부에서 아예 못 본다” 혹은 “변경할 때만 await가 필요하다”는 식의 이해다. 하지만 실제 기준은 read/write 여부보다 먼저 actor 경계를 넘는 접근인지 여부다.
더 정확한 표현은 이렇다. 같은 actor 내부에서는 자기 actor-isolated 상태를 await 없이 접근할 수 있고, 다른 actor나 외부에서 그 상태에 접근하면 cross-actor reference가 되므로 보통 `await`가 필요하다.
`변경할 때만 await`가 아니라, `다른 actor 경계를 넘을 때 await`라고 이해하는 것이 맞다.
`actor-isolated`라는 말은 actor의 저장 프로퍼티나 메서드가 그 actor의 보호 영역 안에 있다는 뜻이다. 따라서 같은 actor 내부에서는 이 멤버들을 자유롭게 참조할 수 있다.
예를 들어 actor 안에서 `self.balance`를 읽거나 `deposit()`을 호출하는 것은 같은 actor 내부 접근이므로 cross-actor reference가 아니다. 이 경우에는 보통 `await`가 필요 없다.
하지만 다른 actor나 일반 외부 코드에서 `account.balance`, `account.deposit()`처럼 접근하면 actor 경계를 넘게 된다. 이때가 cross-actor reference이고, 보통 비동기 접근 규칙에 따라 `await`가 필요하다.
actor BankAccount {
let accountNumber: Int
var balance: Double
init(accountNumber: Int, balance: Double) {
self.accountNumber = accountNumber
self.balance = balance
}
func deposit(amount: Double) {
balance += amount
}
func transfer(to other: BankAccount, amount: Double) async {
balance -= amount
await other.deposit(amount: amount)
}
}위 코드에서 `balance -= amount`는 자기 actor 내부 상태 변경이므로 await가 필요 없다. 반면 `other.deposit(...)`은 다른 actor의 isolated 메서드 호출이므로 await가 필요하다.
actor의 `let` 저장 프로퍼티는 초기화 후 변하지 않기 때문에, 같은 모듈 안에서는 외부에서도 동기 접근이 허용될 수 있다. 예를 들어 `account.accountNumber`는 같은 모듈 안에서 그냥 읽을 수 있다.
반대로 `var`는 mutable state이므로 외부에서 동기적으로 직접 읽고 쓰는 접근이 허용되지 않는다. 외부에서 보려면 보통 `await account.balance`처럼 cross-actor 비동기 접근을 사용해야 한다.
그래서 글에서 말하는 “나중에 `let`을 `var`로 바꾸면 외부 API 계약이 달라진다”는 말은, 기존 동기 접근 코드가 이제는 `await`를 요구하게 된다는 뜻이다.