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

UnsafeContinuationresume이 여러 번 호출되어도 체크하는 로직이 없다. 그렇기 때문에 런타임에 잡아주지 못하여 한 Task가 두 번 깨어나면서 메모리 오염/이상한 값 반환/데드락 등 발생할 수 있다.

Undefined Behavior(정의되지 않은 동작) 이기 때문에 어떤 일이 일어날 지 몰라 이상한 버그가 발생할 가능성이 있다.

CheckedContinuation vs UnsafeContinuation

구분CheckedContinuationUnsafeContinuation
런타임 안전성안전 검사 있음: 한 번만 resume 되었는지, 누락되지 않았는지 디버그에서 검증검사 없음: 실수해도 런타임이 잡아주지 않음
디버깅 난이도문제(이중 resume/미호출) 발견이 쉬움문제 발견이 어려워 디버깅 비용 큼
권장 용도대부분의 브리지 케이스 (일반 앱 코드)성능·제약상 불가피할 때만 (로우레벨/특수 라이브러리)
성능 오버헤드소폭 존재(검사 비용)가장 얇음
실수 시 결과크래시/런타임 워닝으로 빨리 인지조용한 논리 버그, 메모리/리소스 누수 위험
API 이름withCheckedContinuation, withCheckedThrowingContinuationwithUnsafeContinuation, withUnsafeThrowingContinuation

UnsafeContinuation은 위험성이 크기 때문에 정말 이유가 있는 것이 아니라면 CheckedContinuation 사용이 권장된다.

만일 resume이 여러 번 호출 될 가능성이 있다면 continuationnil처리하여 한 번만 호출될 수 있도록 막아야 한다.

guard let continuation = self.continuation else { return }
self.continuation = nil
continuation.resume(returning: value)