상세 컨텐츠

본문 제목

[iOS] Swift Concurrency(2) - Continutation

iOS/iOS 지식

by 홍루피 2024. 11. 11. 15:48

본문

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을 해야 한다는 것임

3333; text-align: start;">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))
                }
            }
            
        }
    }
}

 

참고자료

관련글 더보기