Skip to content

Commit 68b7300

Browse files
committed
Add Actor Post
1 parent a22048e commit 68b7300

File tree

3 files changed

+209
-8
lines changed

3 files changed

+209
-8
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
---
2+
title: Actor 란
3+
date: 2026-02-07 21:30
4+
tags: Swift, Concurrency, Actor
5+
image: thumbnail/actor.svg
6+
description: Actor에 대해서 알아보겠습니다.
7+
---
8+
9+
### Actor 란
10+
`Actor`는 Swift Concurrency에서 Data Race를 컴파일 타임에 방지하기 위해 도입된 reference 타입입니다.
11+
12+
Actor가 나오기 전엔, class는 여러 스레드에서 동시에 접근할 수 있기 때문에, 개발자가 직접 `DispatchQueue`, `NSLock`, `Semaphore` 등을 통해서 동기화를 보장해야했지만, 그로 인해 유지보수 비용이 매우 높아졌다.
13+
14+
하지만 Actor는 이를 언어 차원에서 해결해준다.
15+
16+
17+
### Actor의 핵심 보장
18+
- 한 시점에 단 하나의 Task만 actor의 mutable state에 접근 가능
19+
- 동기화 코드를 직접 작성하지 않아도 됨
20+
- 잘못된 동시 접근은 컴파일 에러로 차단
21+
22+
즉 Actor는 Thread-safe한 class를 기본값으로 제공한다고 하면 이해하기 쉽다.
23+
24+
### Actor의 기본 구조
25+
```swift
26+
actor Counter {
27+
var value: Int = 0
28+
29+
30+
func increment() {
31+
value += 1
32+
}
33+
34+
35+
func getValue() -> Int {
36+
value
37+
}
38+
}
39+
```
40+
41+
사용법
42+
43+
```
44+
let counter = Counter()
45+
46+
47+
Task {
48+
await counter.increment()
49+
let value = await counter.getValue()
50+
print(value)
51+
}
52+
```
53+
54+
`actor` 내부의 `var`은 자동으로 보호되며 외부에서 접근시 반드시 `await`가 필요하고, 내부에서는 `await` 없이 자기 자신의 state 접근 가능하다.
55+
56+
57+
### Actor Isolation
58+
Actor에서는 Isolation이 존재한다.
59+
- actor의 모든 mutable state는 actor의 executor에 격리되게 된다.
60+
- 외부에서는 actor 내부 상태에 직접 접근이 불가하다.
61+
62+
```swift
63+
actor UserStore {
64+
var users: [String] = []
65+
}
66+
67+
68+
let store = UserStore()
69+
// ❌ 컴파일 에러
70+
store.users.append("Jihoon")
71+
```
72+
73+
이 상황에서 아래 처럼 해야한다.
74+
75+
```swift
76+
actor UserStore {
77+
private var users: [String] = []
78+
79+
80+
func add(_ user: String) {
81+
users.append(user)
82+
}
83+
}
84+
85+
86+
await store.add("Jihoon")
87+
```
88+
이 구조 덕분에 데이터 레이스는 구조적으로 불가능합니다.
89+
90+
### Actor vs Class + Lock
91+
92+
기존에는 Class 와 NSLock을 사용해서
93+
94+
```swift
95+
final class SafeCounter {
96+
private var value = 0
97+
private let lock = NSLock()
98+
99+
func increment() {
100+
lock.lock()
101+
value += 1
102+
lock.unlock()
103+
}
104+
}
105+
```
106+
문제점
107+
- lock 누락 가능성
108+
- 데드락 위험
109+
- 가독성 저하
110+
- 테스트 난이도 상승
111+
112+
113+
Actor 방식을 사용하면
114+
```swift
115+
actor SafeCounter {
116+
private var value = 0
117+
118+
func increment() {
119+
value += 1
120+
}
121+
}
122+
```
123+
124+
안정성과 가독성을 올리고, 컴파일러를 보장합니다.
125+
126+
### Non-isolated & Read-only 최적화
127+
128+
Actor의 모든 메ㅅ드가 항상 `await` 를 요구하는건 아니다.
129+
130+
```swift
131+
actor Config {
132+
let apiVersion = "v1"
133+
134+
135+
nonisolated func version() -> String {
136+
apiVersion
137+
}
138+
}
139+
```
140+
141+
- `let` 상수는 동기화가 불필요
142+
- `nonisolated`는 actor executor를 거치지 않음
143+
- 성능 최적화에 매우 중요
144+
- mutable state 접근은 불가
145+
146+
### Reentrancy (재진입성)
147+
Actor는 reentrant하다.
148+
149+
```swift
150+
actor BankAccount {
151+
var balance: Int = 100
152+
153+
func withdraw(_ amount: Int) async {
154+
if balance >= amount {
155+
await Task.yield()
156+
balance -= amount
157+
}
158+
}
159+
}
160+
```
161+
162+
문제는, `await` 중에 다른 task가 끼어들 수 있고, 논리적 경쟁 상태 (logical race) 발생 가능합니다.
163+
이를 해결하려면, 중요한 연산은 `await` 없이 한 번에 처리하며, State snapshot 사용하면 된다.
164+
```swift
165+
func withdraw(_ amount: Int) {
166+
guard balance >= amount else { return }
167+
balance -= amount
168+
}
169+
```
170+
171+
### Global Actor (MainActor)
172+
```swift
173+
@MainActor
174+
class ViewModel {
175+
var title = ""
176+
}
177+
```
178+
179+
특정 executor(주로 Main Thread)에 격리 시킬수 있습니다.
180+
181+
### 성능
182+
Actor같은 경우
183+
- lock 기반 보다 약간 오버헤드가 존재
184+
- context switching 비용
185+
- executor enqueue/dequeue
186+
187+
하지만
188+
- 대부분의 앱에서 체감 불가 수준
189+
- lock 사용에 대한 버그 비용보다는 오버헤드가 감수 할 정도
190+
191+
Actor를 사용하면 안되는 경우
192+
- 초당 수십만번 호출 되는 hot path 경우
193+
- 매우 짧은 atomic 연산
194+
195+
대신
196+
`ManagedAtomic` (Swift Atomics)와 value type + task-local 로 처리 하면 됩니다.
197+
198+
### 언제 사용해야되나
199+
200+
- 공유 상태 관리
201+
- cache, store, repository
202+
- Analytics, Logging
203+
- ViewModel, Domain Service
204+
205+
206+
### 피해야되는 경우
207+
- 수치 연산 중심 코드
208+
- tight looop
209+
- 단일 스레드 보장 환경

Sources/Website/Layouts/ContentDetailLayout.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ struct ContentDetailLayout: HTMLConvertable {
7878
})
7979
}
8080
.class("screen-carousel-dots")
81-
Span {
82-
Text("슬라이드 하면서 페이지 보여줌")
83-
}
84-
.class("screen-carousel-caption")
8581
}
8682
.class("screen-carousel-footer")
8783
}

Sources/Website/Layouts/ProjectLayout.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ struct ProjectLayout: HTMLConvertable {
9898
.class("project-mockup-device-row")
9999
.attribute(named: "data-current-platform", value: defaultPlatformKey)
100100

101-
Span {
102-
Text("슬라이드 하면서 페이지 보여줌")
103-
}
104-
.class("device-carousel-caption")
105101
}
106102
.class("project-mockup-device")
107103
}

0 commit comments

Comments
 (0)