|
| 1 | +--- |
| 2 | +title: Swift Concurrency |
| 3 | +date: 2026-02-06 21:24 |
| 4 | +tags: Swift, Concurrency |
| 5 | +image: thumbnail/swift-concurrency.svg |
| 6 | +description: Swift Concurrency에 대해서 알아보겠습니다. |
| 7 | +--- |
| 8 | + |
| 9 | +### Swift Concurrency란 |
| 10 | + |
| 11 | +Swift 5.5에서 나온 Swift Concurrency는 단순하게 `DispatchQueue`를 대체 하는것이 아닌 다양한 문제를 해결해 주는 강력한 도구이다. |
| 12 | + |
| 13 | +- 비동기 로직이 늘어날 때 completion handler 지옥 발생 |
| 14 | +- 상태 기반 아키텍처 등에서 비동기 이벤트 스트림 처리의 복잡도 증가 |
| 15 | + |
| 16 | +### DispatchQueue, Swift Concurrency 비교 |
| 17 | + |
| 18 | +일단 먼저 DispatchQueue의 본질은 |
| 19 | + |
| 20 | +DispatchQueue는 아래 책임을 가진다. |
| 21 | +- 작업(Block)을 queue에 넣는다 |
| 22 | +- 스레드 풀에 작업을 분배 |
| 23 | +- 직렬 / 병렬 실행을 보장한다 |
| 24 | + |
| 25 | +```swift |
| 26 | +DispatchQueue.global().async { ... } |
| 27 | +``` |
| 28 | + |
| 29 | +위 코드에서 GCD는 언젠가 실행되는것과, 현재 스레드 블로킹 하지 않음을 보장한다. |
| 30 | +그리고 이 작업의 소유자, 취소 가능 여부, 다른 작업과의 관계, 데이터 레이스 방지, 상태 일관성 등은 보장하지 않는다. |
| 31 | + |
| 32 | +즉 DispatchQueue는 실행기(executor)이지, 동시성 모델이 아니다. |
| 33 | + |
| 34 | +#### DispatchQueue기반 동시성의 한계 |
| 35 | + |
| 36 | +```swift |
| 37 | +var count = 0 |
| 38 | + |
| 39 | +DispatchQueue.global().async { |
| 40 | + count += 1 |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +이 코드는 컴파일이 성공 하고, 런타임 겅공이 가능하며, 결과는 비결정적이며 데이터레이스는 전적으로 개발자의 책임이다. |
| 45 | + |
| 46 | +```swift |
| 47 | +let queue = DispatchQueue(label: "counter") |
| 48 | + |
| 49 | +queue.async { |
| 50 | + count += 1 |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +해결하기 위해서 위 코드 처럼 작성하면, 결국 설계 부담이 전부 개발자에게 오게 된다. |
| 55 | + |
| 56 | +### Swift Concurrency의 핵심 철학 |
| 57 | + |
| 58 | +1. Structed Concurrency |
| 59 | +2. Cooperative Thread Pool (Swift Runtime) |
| 60 | +3. Date Race를 컴파일 타임에 차단하는 시도 |
| 61 | + |
| 62 | +```swift |
| 63 | +async let a = fetchA() |
| 64 | +async let b = fetchB() |
| 65 | +let result = await a + b |
| 66 | +``` |
| 67 | + |
| 68 | +이런 식으로 사용하게 되면 이러한 장점을 챙길 수 있다. |
| 69 | + |
| 70 | +- `a`, `b`의 부모 스코프에 생명주기가 종속됨 |
| 71 | +- 부모 Task 가 cancel 되면 자식 Task 도 함께 cancel 됨 |
| 72 | +- Task Tree가 명확해져서 디버깅 및 추론이 쉬워짐 |
| 73 | + |
| 74 | + |
| 75 | +Swift Concurrency로 이렇게 작성하면 |
| 76 | + |
| 77 | +```swift |
| 78 | +Task { |
| 79 | + await doWork() |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +이러한 단계를 거친다. |
| 84 | + |
| 85 | +1. Task 객체 생성 |
| 86 | +2. 부모 Task 에 구조적으로 연결 |
| 87 | +3. 취소 전파 경로 확보 |
| 88 | +4. 실행은 Executor에 위임 |
| 89 | +5. Actor isolation 검사 |
| 90 | + |
| 91 | +즉 Swift Concurrency는 실행을 직접하는것이 아닌, 실행은 `Executor`가 하고 그 밑에 GCD 가 존재한다. |
| 92 | + |
| 93 | +### Swift Concurrency 동작 |
| 94 | +#### Executor |
| 95 | +- Swift Concurrency의 실행 단위 |
| 96 | +- Task를 실제 스레드에 매핑 |
| 97 | +- 대표적인 Executor |
| 98 | + - MainActor executor -> main thread |
| 99 | + - Global executor -> cooperative thread pool |
| 100 | + |
| 101 | +#### 내부 구조 |
| 102 | +- Global executor은 libdispatch(GCD) 위에서 동작 |
| 103 | +- cooperative scheduling (thread 점유 최소화) |
| 104 | + |
| 105 | +> Swift Concurrency → Executor → GCD → Kernel Thread |
| 106 | +
|
| 107 | +방식으로 구동되며, DispatchQueue가 대체 된다고 보다는 밑으로 내려간게 맞다. |
| 108 | + |
| 109 | +### 예제 |
| 110 | +#### DispatchQueue |
| 111 | +```swift |
| 112 | +func loadUser(completion: @escaping (User) -> Void) { |
| 113 | + DispatchQueue.global().async { |
| 114 | + let user = fetchUser() |
| 115 | + DispatchQueue.main.async { |
| 116 | + completion(user) |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +#### Swift Concurrency |
| 123 | +```swift |
| 124 | +func loadUser() async throws -> User { |
| 125 | + try await fetchUser() |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +DispatchQueue랑 Swift Concurrency는 표현력부터 다르다. |
| 130 | + |
| 131 | +현재 위 두개의 예시를 비교 하면, DispatchQueue는 흐름이 분산되고, 에러 전달도 복잡하고, 취소가 불가능하며 테스트의 어려움을 겪을수 있다. |
| 132 | + |
| 133 | +하지만 Swift Concurrency를 사용한 곳에서는, 동기 코드 처럼 가독성이 올라갔고, 에러 전파가 자동으로 되며, Task cancel이 가능하고 테스트가 용이 하다는 장점이 있다. |
| 134 | + |
| 135 | + |
| 136 | +### 구조적 동시성 vs 비구조적 동시성 |
| 137 | + |
| 138 | +DispatchQueue |
| 139 | +- 작업간 관계 없음 |
| 140 | +- fire-and-forget |
| 141 | +- 추적 불가 |
| 142 | + |
| 143 | +Swift Concurrency |
| 144 | +- 부모-자식 Task 트리 |
| 145 | +- 취소 / 에러 전파 |
| 146 | +- 생명주기 명확 |
| 147 | + |
| 148 | +### 결론 |
| 149 | + |
| 150 | +DispatchQueue를 써야 할 때 |
| 151 | +- C / legacy API 래핑 |
| 152 | +- barrier, concurrent queue 제어 |
| 153 | +- low-level synchronization |
| 154 | +- 성능 실험 |
| 155 | + |
| 156 | +Swift Concurrency를 써야 할 때 |
| 157 | +- 앱 레벨 비동기 로직 |
| 158 | +- 네트워크 / IO |
| 159 | +- 상태 기반 아키텍처 |
| 160 | +- 테스트 가능한 코드 |
0 commit comments