Swift Concurrency에서 completion handler나 delegate와 같이 콜백 기반 비동기 코드를 async/await 스타일로 변경하고자 할 때 사용한다.
비동기 메서드 안에서 continuation을 등록하고, 비동기 task를 수행 완료한 후 resume을 호출하여 작업을 이어서 처리할 수 있다.
resume은 무조건 한 번 호출되어야 한다. resume은 무조건 한 번만 호출되어야 하기 때문에 여러번 호출되면 시스템은 정의되지 않은 동작으로 여기게 된다.
✔️ CheckedContinuation
콜백 기반 코드와 비동기 코드 사이를 안전하게 연결해준다.
위에서 언급한 resume메서드가 여러 번 호출되면 크래시를 발생시킨다. resume호출 여부를 체크 과정이 들어가기 때문에 여러 번 호출되면 즉시 런타임 오류를 발생시킨다.
await withCheckedContinuation { continuation in
continuation.resume(returning: 1)
continuation.resume(returning: 2) // 🚨 런타임 크래시 (Illegal instruction)
}사용 예시
withCheckedContinuation
func getValueAsync() async -> String {
await withCheckedContinuation { (continuation: CheckedContinuation<String, Never>) in
legacyFetch { value in
continuation.resume(returning: value)
}
}
}에러 케이스를 처리할 수 있는 withCheckedThrowingContinuation
enum LegacyError: Error { case failed }
func getValueAsync() async throws -> String {
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<String, Error>) in
legacyFetchWithError { value, error in
if let error { continuation.resume(throwing: error) }
else { continuation.resume(returning: value) }
}
}
}✔️ UnsafeContinuation
UnsafeContinuation은 resume이 여러 번 호출되어도 체크하는 로직이 없다. 그렇기 때문에 런타임에 잡아주지 못하여 한 Task가 두 번 깨어나면서 메모리 오염/이상한 값 반환/데드락 등 발생할 수 있다.
Undefined Behavior(정의되지 않은 동작) 이기 때문에 어떤 일이 일어날 지 몰라 이상한 버그가 발생할 가능성이 있다.
✔️ CheckedContinuation vs UnsafeContinuation
| 구분 | CheckedContinuation | UnsafeContinuation |
|---|---|---|
| 런타임 안전성 | 안전 검사 있음: 한 번만 resume 되었는지, 누락되지 않았는지 디버그에서 검증 | 검사 없음: 실수해도 런타임이 잡아주지 않음 |
| 디버깅 난이도 | 문제(이중 resume/미호출) 발견이 쉬움 | 문제 발견이 어려워 디버깅 비용 큼 |
| 권장 용도 | 대부분의 브리지 케이스 (일반 앱 코드) | 성능·제약상 불가피할 때만 (로우레벨/특수 라이브러리) |
| 성능 오버헤드 | 소폭 존재(검사 비용) | 가장 얇음 |
| 실수 시 결과 | 크래시/런타임 워닝으로 빨리 인지 | 조용한 논리 버그, 메모리/리소스 누수 위험 |
| API 이름 | withCheckedContinuation, withCheckedThrowingContinuation | withUnsafeContinuation, withUnsafeThrowingContinuation |
UnsafeContinuation은 위험성이 크기 때문에 정말 이유가 있는 것이 아니라면 CheckedContinuation 사용이 권장된다.
만일 resume이 여러 번 호출 될 가능성이 있다면 continuation을 nil처리하여 한 번만 호출될 수 있도록 막아야 한다.
guard let continuation = self.continuation else { return }
self.continuation = nil
continuation.resume(returning: value)✔️ 그렇다면 왜 UnsafeContinuation이 존재할까?
문득 의문이 들었다.
안정적이지도 않고, 꽤 심각한 문제를 일으킬 수 있는데 swift에는 대체 왜 UnsafeContinuation이 있는걸까?
1. 성능 최적화
CheckedContinuation은 런타임 검사를 수행하게 되는데, 그 과정에서 약간의 성능 오버헤드가 있다.
반면 UnsafeContinuation은 아무런 검사를 수행하지 않기 때문에 비교적 빠르게 동작한다.
→ 1초에 수천번 이상의 호출이 발생하는 상황에서는 이 차이가 꽤나 중요하게 여겨질 수 있다.
2. 이미 안정적인 환경
어떤 시스템 API는 콜백이 정확히 한 번만 호출 되는것을 보장한다.
이러한 경우 resume이 중복으로 호출되는지 여부를 검사하는건 불필요한 과정이 될 것이다.
그렇게 되면 굳이 CheckedContinuation의 오버헤드를 감수할 이유가 없어지는 것이다.

위의 예시는 NWConnection.receiveMessage(completion: )문서의 내용이다.
문서에는 “A receive completion is invoked exactly once for a call to receive” 라고 명시가 되어있는데, 이 경우 UnsafeContinuation을 사용해 볼 수 있을것이다.
그치만.. 문서상에서 한 번만 호출이 된다는 보장이 있다고 해도 100% 보장된다고 볼 순 없다.
네트워크 오류나 시스템 오류로 인해 callback이 호출되지 않을 가능성이 있기 때문이다.
그렇기 때문에 대부분의 경우에는 CheckedContinuation을 사용하는게 안정적인 방법인 것 같다.