Continuation이라는 개념이 있음
이전에 Swift Concurrency에서 wait-resume을 통해 쓰레드의 제어권을 시스템에 반환하고 돌려받는 과정이 있다고 했음!
여기서 작업이 재개될 때 스레드는 변할 수 있음
왜냐하면 애플이 스레드 관리를 시스템에서 하기 때문에 추가적인 스레드의 생성 없이 적절한 스레드를 배정해주기 때문임
이 과정이 있기 때문에 Swift Concurrency는 스레드 익스플로전을 방지할 수 있다고 볼 수 있음
스레드 익스플로전은 뭔가요?
CPU 코어보다 스레드의 수가 많아지는 것을 말함
스레드가 과도하게 생성되면 일 많이 하고 좋은 거 아닌가요? 라고 생각할 수 있음
하지만 과한 스레드 생성은 빈번한 컨텍스트 스위칭으로 인한 오버헤드나, 블락된 스레드를 기다리면서 가지고 있는 메모리 때문에 메모리 오버헤드를 발생시킬 수 있다고 함
문맥교환(Context Switching)은 뭔가요?
- CPU를 사용하는 주체가 변경되는 상황에 문맥교환이 일어남
- CPU를 여러 쓰레드가 사용할 때 타임슬라이스 단위로 CPU를 나누어서 사용하는데, 이때 실행중이던 작업을 보존하게 됨
- 작업이 재개되었을 때 다시 보존된 작업을 가져오게 되는데 이 과정에서 오버헤드 발생
문맥교환에 대한 부분은 아래 블로그 참고!
그래서 Continuation이 뭘까요?
특정 지점에서 함수의 실행 컨텍스트를 추적할 수 있는 객체를 말함
이 Continuation을 사용해서 컨텍스트 스위칭을 단순히 함수 호출 정도의 리소스를 사용해 스케쥴링 리소스를 줄인다고 함
여기서 스레드를 추가적으로 생성하지 않고 스레드 재사용의 관점에서 스레드 익스플로전도 방지할 수 있음
이 과정에서 heap과 stack에서 변수들이 저장되고 교체되는 과정이 있는데 이건 아래 참고자료를 읽어보시면 좋을듯함!
컨셉적인 것은 이해를 했으니 한 번 사용관점에서 봐보겠음
비동기적인 작업을 Completion Handler의 형태로 응답을 받는 경우가 있음
받아서 이후 처리를 해야 하는데 async-await으로 사용하기에 형태가 맞지 않음
이 경우에 해당 실행 컨텍스트를 추적해서 직접 처리를 해줄 수 있음
이렇게 resume을 통해서 에러를 던지거나 값을 전달해 줄 수 있음
이 외에도 CLLocation에서의 위치업데이트, Kingfisher의 retrieveImage 등에서 컴플리션 핸들러 형태로 응답이 올 때 continuation을 사용해서 완료시점에 대한 처리를 해줄 수 있음
이 Continuation을 사용할 때는 규칙을 지켜야 하는데 항상 모든 경로에서 한번은 resume을 해야 한다는 것임
resume을 하지 않으면 await이 계속 발생하면서 리소스 낭비로 이어질 수 있음
CheckedContinuation과 UnSafeContinuation이 있음
CheckedContinuation은 여러번 resume이 되지 않는지 런타임 검사가 수행됨
UnsafeContinuation은 오버헤드를 줄이고 속도적인 측면을 강조하기 때문에 불변성을 체크하지 않음(검사 수행X)
예시로 Kingfisher에서 이미지를 가지고 오는 코드를 작성해 봄
continuation과 ResultType을 이용해서 async await 형태로 이미지를 가져와서 보여줄 수 있음
extension ContinuationViewController {
private func configureImage() {
Task {
let result = await fetchImage()
switch result {
case .success(let value):
imageView.image = value
case .failure(let error):
view.makeToast("오류가 발생하였습니다")
}
}
}
enum ImageFetchError: Error {
case invalidURL
case noImage
}
private func fetchImage() async -> Result<UIImage, ImageFetchError> {
return await withCheckedContinuation { continuation in
guard let url = URL(string: "https://picsum.photos/200/300") else {
return continuation.resume(returning: .failure(.invalidURL))
}
KingfisherManager.shared.retrieveImage(with: url) { result in
switch result {
case .success(let value):
let image = value.image
return continuation.resume(returning: .success(image))
case .failure(let error):
return continuation.resume(returning: .failure(.noImage))
}
}
}
}
}
참고자료
'iOS > iOS 지식' 카테고리의 다른 글
[iOS] StackView의 Distribution과 Alignment (0) | 2024.11.28 |
---|---|
Swift Concurrency(1) - Async와 Await (2) | 2024.11.10 |
[Swift] Protocol(1) - (some과 any는 왜 필요할까?) (0) | 2024.10.07 |
[Swift] Struct와 Class (1) - Class 안에 Class, Struct 안에 Class (0) | 2024.10.05 |
[iOS] ViewController 생명주기(2) - 실험 (2) | 2024.10.04 |