반응형
Continuation
Continuation
- Continuation는 동기식 코드와 비동기식 코드 간의 인터페이스를 위한 메커니즘이다.
- 즉, 동기식 코드를 비동기식 코드에 이식할 때 간단하게 인터페이스를 제공해줄 수 있는 것이다.
- 그 종류에는 정확성 체크를 진행하는 CheckedContinuation와 체크하지 않는 UnsafeContinuation가 있다.
- 정확성 체크란 누락되거나 여러 번 재개된 작업이 있는지 런타임 검사를 수행하는 것을 의미한다.
- UnsafeContinuation는 오버헤드가 적은 것을 목표로 하기 때문에 불변성을 체크하지 않는다.
- 대표적으로 사용하는 함수는 withCheckedThrowingContinuation(withCheckedContinuation)이다.
func getData(url: URL) async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
continuation.resume (returning: data)
} else if let error = error {
continuation.resume (throwing: error)
}
}
}
}
- 이런식으로 일반적인 동기식 코드를 async 스타일에 맞도록 만들어준다.
- resume을 통해 데이터를 비동기적으로 리턴할 수 있다.
- Continuation을 사용할 때 지켜야할 규칙이 있다.
- 기본적으로 resume은 단 한번 호출되어야 한다. 두 번 이상 resume이 호출되면 예상치 못한 동작이 발생한다.
- 아예 resume을 호출하지 않으면 코드가 영원히 await하게 된다. 이는 리소스 낭비로 이어진다.
CheckedContinuation Vs UnsafeContinuation
- 우선 UnsafeContinuation를 사용해보자.
func getData(url: URL) async throws -> Data {
return try await withUnsafeThrowingContinuation { continuation in
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
continuation.resume (returning: data)
continuation.resume (returning: data)
} else if let error = error {
continuation.resume (throwing: error)
}
}
}
}
Task {
guard let url = URL(string: "<https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.joongang.co.kr%2Farticle%2F25121160&psig=AOvVaw31JuPFpNklL68IoZGm0v12&ust=1678975033567000&source=images&cd=vfe&ved=0CBAQjRxqFwoTCOi_xMWL3v0CFQAAAAAdAAAAABAE>") else {
print("Url error")
return
}
do {
let data = try await getData(url: url)
print(data)
} catch {
print(error)
}
}
// Result
// 1814 bytes
- continuation에 두번 resume을 했음에도 정상적으로 동작이 수행되는 것처럼 보인다.
- 다만 이는 예상치 못한 에러를 야기할 수 있는 위험한 코드이다. 에러의 이유도 알 수 없다.
- 다음으로 CheckedContinuation를 사용해보자.
func getData(url: URL) async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
continuation.resume (returning: data)
continuation.resume (returning: data)
} else if let error = error {
continuation.resume (throwing: error)
}
}
}
}
Task {
guard let url = URL(string: "<https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.joongang.co.kr%2Farticle%2F25121160&psig=AOvVaw31JuPFpNklL68IoZGm0v12&ust=1678975033567000&source=images&cd=vfe&ved=0CBAQjRxqFwoTCOi_xMWL3v0CFQAAAAAdAAAAABAE>") else {
print("Url error")
return
}
do {
let data = try await getData(url: url)
print(data)
} catch {
print(error)
}
}
// Result
// 1814 bytes
// _Concurrency/CheckedContinuation.swift:164: Fatal error: SWIFT TASK CONTINUATION MISUSE: getData(url:) tried to resume its continuation more than once, returning 1814 bytes!
- 두번 resume을 수행하려고 하면 명시적으로 에러가 발생함을 알 수 있다.
- 이를 통해 개발자는 자신의 실수를 파악할 수 있을 것이다.
func getData(url: URL) async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
URLSession.shared.dataTask(with: url) { data, response, error in
}
.resume()
}
}
Task {
guard let url = URL(string: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.joongang.co.kr%2Farticle%2F25121160&psig=AOvVaw31JuPFpNklL68IoZGm0v12&ust=1678975033567000&source=images&cd=vfe&ved=0CBAQjRxqFwoTCOi_xMWL3v0CFQAAAAAdAAAAABAE") else {
print("Url error")
return
}
do {
let data = try await getData(url: url)
print(data)
} catch {
print(error)
}
}
// SWIFT TASK CONTINUATION MISUSE: getData(url:) leaked its continuation!
- resume을 아예 하지 않은 경우도 마찬가지이다.
반응형
'🍎 Apple > Concurrency & GCD' 카테고리의 다른 글
[Concurrency] Explore structured concurrency in Swift (1) | 2023.10.29 |
---|---|
[Concurrency] Actor (0) | 2023.03.31 |
[Concurrency] Task (0) | 2023.03.31 |
[Concurrency] async / await (2) | 2023.03.31 |
[Swift] GCD 정리하기 (2) | 2022.05.30 |