티스토리 뷰
Swift Concurrency의 핵심: Structured Concurrency로 안전한 비동기 작업 제어하기
Swift Concurrency를 활용하여 비동기 처리를 구현하고 계시다면, Structured Concurrency(구조화된 동시성) 형태로 작업을 구성하는 것이 강력하게 권장됩니다. 왜냐하면 이는 작업 간의 부모/자식 관계를 명시적으로 설정하여, 복잡한 비동기 작업을 예측 가능하고 유연하게 제어할 수 있게 하기 때문입니다.
오늘은 이 원칙의 가장 큰 장점인 '취소(Cancellation) 전파' 기능을, 중첩된 Task 작업이 있는 상황을 가정해서 코드 작성해보며 실험해보겠습니다.
playground 에서 emtpy project를 생성해서 swift concurrency 기반 코드 작성 및 테스트를 해보실 수 있습니다!
1. Unstructured Concurrency: 취소 신호가 멈추는 곳
Task { ... } 블록 내에서 새로운 Task { ... }를 생성하는 방식은 Unstructured Concurrency (비구조화된 동시성)의 대표적인 예입니다.
새로 생성된 Task는 바깥 Task의 컨텍스트(예: actor 컨텍스트)는 물려받을 수 있지만, 별도의 최상위 비동기 작업으로 간주됩니다. 이 때문에 부모 Task의 취소 신호가 자식 Task로 자동으로 전파되지 않습니다. 이 방식은 세부 비동기 작업의 생명 주기 제어가 어렵다는 명확한 단점을 가집니다.
실험 1: 취소 전파 실패 (Unstructured Task)

// outerTask와 innerTask는 별도의 최상위 비동기 작업으로 간주됩니다.
let outerTaskWithUnstructuredConcurrency = Task {
// ⚠️ 이 innerTask는 outerTask와 독립적인 별도의 Task입니다.
let innerTask = Task {
for num in 1...5 {
// Task.sleep은 취소 가능 지점입니다.
try? await Task.sleep(for: .seconds(1))
try Task.checkCancellation()
debugPrint("inner task \(num) 초 with unstructured concurrency")
}
}
do {
// innerTask의 완료를 기다립니다. (innerTask의 value를 await)
try await innerTask.value
} catch is CancellationError {
// 이 catch는 innerTask 내부의 오류가 아닌,
// outerTask 내부에서 발생한 CancellationError만 잡습니다.
debugPrint("inner task cancelled with unstructured concurrency")
}
}
// outerTask 취소 신호가 innerTask로 전달되지 않습니다.
outerTaskWithUnstructuredConcurrency.cancel()
// 📋 출력 결과 (취소 신호를 무시하고 5초까지 완료)
// "inner task 1 초 with unstructured concurrency"
// ...
// "inner task 5 초 with unstructured concurrency"
2. Structured Concurrency: TaskGroup을 통한 안전한 제어
TaskGroup은 Swift Concurrency의 Structured Concurrency 개념 중 하나입니다. withTaskGroup이나 withThrowingTaskGroup을 사용하면 명시적인 부모-자식 관계가 형성되어, 부모 Task의 생명 주기가 자식 Task의 생명 주기와 강하게 연결됩니다.
실험 2: 취소 전파 성공 (Structured TaskGroup)

func sampleTaskGroup() async throws {
// withThrowingTaskGroup: 이 블록 내의 모든 Task는 부모 Task의 자식이 됩니다.
try await withThrowingTaskGroup(of: Void.self) { group in
// group.addTask: 자식 Task를 생성하여 그룹에 추가합니다.
group.addTask {
for num in 1...5 {
try await Task.sleep(for: .seconds(1))
// 부모 Task가 취소되면 여기서 CancellationError가 발생합니다.
try Task.checkCancellation()
debugPrint("inner task \(num) 초 with structured concurrency")
}
}
// 부모 Task가 자식들의 완료를 기다리도록 보장합니다.
for try await _ in group { }
}
}
let outerTaskWithStructuredConcurrency = Task {
do {
// sampleTaskGroup을 호출함으로써 부모 Task와 자식 TaskGroup 간의 관계가 형성됩니다.
try await sampleTaskGroup()
} catch is CancellationError {
// outerTask가 취소되면, sampleTaskGroup이 CancellationError를 던지고 이 곳에서 포착됩니다.
debugPrint("inner task cancelled with structured concurrency")
} catch {
debugPrint("other error : \(error) with structured concurrency")
}
}
// outerTask 취소 신호가 TaskGroup을 통해 innerTask로 전파됩니다.
outerTaskWithStructuredConcurrency.cancel()
// 📋 출력 결과 (취소 신호 감지 후 즉시 종료)
// (1초 경과 로그 후)
// "inner task cancelled with structured concurrency"
결론 및 활용

지금까지 중첩 비동기 작업이 존재할때의 swift concurrency 사용예시를 몇가지 작성 및 실험해봤습니다. 한번 더 내용을 정리해보자면...
TaskGroup, async let, AsyncStream 등의 Structured Concurrency 도구들은 비동기 작업에 명확한 계층 구조를 부여하여 다음을 가능하게 합니다.
- 안전한 취소: 부모가 취소되면 관련된 모든 자식 작업이 자동으로 정리됩니다.
- 쉬운 에러 핸들링: 자식 Task에서 발생한 오류는 부모 Task로 명확하게 전달됩니다.
Swift Concurrency의 작업이 복잡해질 때, 단순 Task { ... } 대신 TaskGroup과 async let을 활용하는 것이 코드를 더 예측 가능하고 안정적으로 만드는 핵심 비결입니다.
'iOS 개발 > iOS 개발 팁' 카테고리의 다른 글
| Swift Concurrency, Task와 GCD: QoS 우선순위 설정 사용 방법 (0) | 2025.11.01 |
|---|---|
| iOS 개발, Cocoa Touch 개념 및 UIKit, Foundation과의 관계 (0) | 2025.10.10 |
| iOS AppDelegate, 앱 초기설정 및 생애주기 관리 방법 (0) | 2025.10.06 |
| iOS26 개발, 시뮬레이터 테스트 위한 Xcode26 설치 방법 (0) | 2025.09.26 |
| App Store Connect 애플 개발자 계정 비밀번호 변경하는 방법 (0) | 2025.09.06 |
- Total
- Today
- Yesterday
- 부스트코스
- 김프매매
- createML
- swift 기초
- swift알고리즘
- 자연어처리
- 프로그래머스swift
- 프로토콜
- swift언어
- Protocol
- CoreML
- swift 문자열
- swift reduce
- uikit
- swift문제
- swift
- ios
- swift concurrency
- swift string
- SwiftUI
- 알고리즘
- 컬렉션
- 개발자문서
- Collection
- 스위프트
- 프로그래머스
- Swift 알고리즘
- 백준알고리즘
- 알고리즘문제
- 백준swift
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |