Swift Concurrency 이전의 DispatchQueue (GCD)가 나오게 된 배경
이전의 Grand Central Dispatch(GCD)는 정확히 당신이 생각한 대로 thread pool을 시스템 레벨에서 관리하여 thread explosion을 방지하기 위해 만들어졌음
GCD 이전의 문제점들
Thread 직접 관리의 어려움:
- 개발자가 직접 Thread를 생성하고 관리해야 했음
- Thread 생성 비용이 높고(스택 메모리 할당, 커널 데이터 구조 등), 각 Thread마다 최소 512KB의 메모리 필요
- Thread explosion: 동시성 작업이 많아지면 무분별하게 Thread가 생성되어 시스템 리소스 고갈
Apple 문서의 표현:
"In the past, introducing concurrency to an app required the creation of one or more additional threads. Unfortunately, writing threaded code is challenging. Threads are a low-level tool that must be managed manually."
GCD의 핵심 이점:
- Thread Pool 자동 관리: 시스템이 최적의 Thread 개수 결정
- Thread 재사용: 작업 완료 후 Thread를 풀에 반환
- Context Switching 최소화: 과도한 Thread 생성 방지
- 개발자 친화적: Thread 생명주기 관리 불필요
Swift Concurrency가 나오게 된 배경
GCD는 훌륭했지만, 10년 이상 사용하면서 근본적인 문제들이 있었고, 그걸 해결하기 위한 방법으로 Swift Concurrency가 나옴
문제는 다음과 같음
- allback hell ( 콜백지옥 ) - 가독성문제
- 여전히 존재하는 thread explosion
- data race(경합)
- 취소와 우선순위의 관리 어려움 - 구조적 cancellation으로 해결
- UI 변경의 취약성
Swift Concurrency의 혁신
Apple 문서:
"Swift Concurrency uses a cooperative thread pool. Instead of blocking threads, async functions suspend, allowing the thread to pick up other work."
위의 문제를 다음과 같이 해결함
- 콜백지옥 - 구조화된 동시성
- thread explosion - non blocking방식의 스레드 관리
- Thread Pool 크기는 CPU 코어 수로 고정 (기본적으로)
- 데이터 경합 - actor를 통해서 해결
- 취소와 우선순위 - Cancel의 전파
- UI 변경 취약성 - mainActor
비교 요약표
| Thread 관리 | Thread Pool (수동 블로킹) | Cooperative Thread Pool (자동 suspend) |
| 가독성 | Callback 중첩 | async/await 순차적 |
| Thread Explosion | 블로킹 시 발생 가능 | 구조적으로 방지 |
| Data Race | 런타임 크래시 | 컴파일 타임 검증 (Actor) |
| 취소 | 수동, 복잡 | 자동 전파 (Structured) |
| 에러 처리 | Result/completion | throws/try-catch |
| UI 스레드 전환 | 수동 (DispatchQueue.main) | 자동 (@MainActor) |
Task
비동기 적인 일처리의 기본 단위
서로다른 Task는 병렬적으로 일처리 가능
비동기 함수는 Task내부에서만 호출 가능하다.(비동기 실행 환경을 만듬)
- 비유하자면, Task는 DispatchWorkItem이면서 ( 취소가 가능하며 한개의 작업단위 ) , 병렬적인 작업 ( DispatchQueue) 의 합본같은 느낌이다. ( 물론 느낌만 그렇다는것 )
생성자
Task(priority: TaskPriority, operation: () async throws -> Sendable)
Task(priority: TaskPriority, operation: () async -> Sendable)
Task안에서는 순서를 가지만, 해당 Task는 한개의 작업단위로 각 스레드로 보내서 작업을 할수 있다.
Task의 특징
- Task자체는 cancle할수있다.
//작업 취소
task.cancle
- Task값을 리턴하는것도 가능하다(generic으로 생성할수 있기 때문)
// Generic으로 리턴값을 정할수도 있다.
let task: Task<String, Never> = Task { return String }
//task.value로 접근 가능
- 작업은 우선순위를 가진다.
- userInitiated -25
- hight - 25
- medium - 21
- low - 17
- utiltiy - 17
- background - 9
기본값은 utility이다.
- 작업은 실행 컨텍스트의 메타데이터를 그대로 상속해서 사용한다. - 굉장히 중요한 개념
- 우선순위
- 실행 액터
- 로컬변수
- 취소 - 는 상속되지 않는다.
하지만 해당 작업을 detached라는 함수를 통해서 독립적으로 실행되게 할수 있다. 이러한 경우에는 상속을 받지 않는다.
기존 GCD와의 차이
간단한 예시 코드를 보면서 이해해보자
func someFunction() async -> String {
sleep(5)
try? await Task.sleep(.second(5))
return "hi"
}
위의 함수를 보면 내부적으로 sleep(5)와 try? await Task.sleep()이 있다.
- sleep의 경우가 기존의 GCD와 동일한 방식이라고 생각하면된다.
- try? await이 이번에 생긴 async await 방식
뭐가다른가?

sleep의 경우에는 thread를 블락한다.
즉 해당 스레드는 5초간 아무일도 못하게 된다.
하지만 concurrency의 경우 해당 함수를 “잠깐 멈춰두고” 다른 일을 할수 잇게 된다.
에러처리
Task로 감싼 경우, 에러를 Task에서 처리해줄수 있다.
- 최근에는 DispatchQueue.main.async를
- await MainActor.run { } 을 사용하면됨
Continuation 사용
- continuation객체란, 함수의 실행이 일시중단될때 현재 함수의 상태를 힙에 저장하게 되는데 이때, 지역변수 실행 위치등을 continuation객체에 저장하여 캡쳐하고, 이를 함수가 재개될때 해당 시점부터 시작될수 있도록 하는 객체이다. ( 사실상 그냥 suspension point)
- 그렇기 때문에 resume()이라는 메소드를 사용하게 되는것임.
continuation은 두개가 있는데
- checked continuation : 컴파일타임에 resume이 두개 실행됐는지 확인
- unsafe continuation : resume이 두개 있는지 런타임에 확인
continuation을 통해서 delegate를 concurrency 형태로 사용할수 있도록 하는
continuation 객체를 생성해서 사용할수 있다.
var checkedContinuation: CheckedContinuation<CLLocation, Error?>
func getCurrentLoaction() async throws {
let location: CLlocation = try await withCheckedContinuation { continuation in
self.checkedContinuation = continuation
}
}
checkedContinuation?.resume(with: .success(CLLocation()))
이러한 방식을 통해서 checkedContinuation을 밖에서 생성해두고, 해당 객체에 resume을 하는 타이밍을 넘기는것으로 delegate메소드 내에서 해당 메소드를 호출하면, 외부에 생성된 객체가 이벤트를 return해준다.
좀더 자세한 예시를 보자
- Delegate메소드를 사용하는 객체 생성
final class locationManager: NSObject, CLLocationManagerDelegate {}
- 해당 객체 내부에 continuation생성
var continuation: ChekcedThrowingContinuation<CLLocation, Error?>
- 해당 continuation을 외부에서 주입
func getLocation() async { do { let location = try await withCheckedContinuation { coninuation in locationManager.continuation = continuation locaationManger.start() } - 주입해주는 시점은 비동기 메소드를 호출해주는시점에서 withCheckedContinuation 을 호출해서, continuation을 생성후 넘겨줌
- delegate 메소드 내부에서 continuation으로 값을 보내줌
- func loactionManager() { continuation?.resume(retiurning: location) continuation = nil }
만약 여러번의 위치를 받아오거나, 계속해서 값이들어오는경우에는 asnycStream으로 처리가능 (추후 작성하겠습니다. )
'앱 > Swift' 카테고리의 다른 글
| [iOS] ReactorKit을 Combine으로 구현하는 법 (0) | 2026.01.18 |
|---|---|
| [iOS] moya를 combine을 이용해서 구현해보자 (0) | 2026.01.18 |
| Reactorkit의 Pulse 구현부에 대해서 (0) | 2025.02.18 |
| Moya Mock Data 사용하기(feat. test Double) (0) | 2025.01.31 |
| reactorkit에서 testcode 작성 하는법(feat. nimble) (0) | 2025.01.31 |