본문 바로가기
Swift

[Swift] 비동기 메소드를 async/await 메소드로 변환하는 방법 (`withUnsafeContinuation`)

by Jimmy_iOS 2024. 3. 2.

Swift의 비동기 메소드를 async/await 메소드로 변환하기

Swift 5.5는 비동기 프로그래밍을 간소화하는 새로운 기능을 도입했는데, 그 중 하나가 async/await 패턴입니다.

이 패턴을 사용하면 비동기 코드를 알기 쉽고 깔끔하게 작성할 수 있습니다. 하지만 이미 작성된 비동기 메소드를 새로운 async/await 메소드로 변환해야 할 때가 있습니다.

예를 들어, 아래와 같은 비동기 메서드가 있다고 가정합니다.

이 코드를 수정할 수 없다면,

어떻게 async/await 메서드에서 이 메서드를 호출할 수 있을까요?

func download(url: URL, completionHandler: @escaping (Result<Data, Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        if let data = data {
            completionHandler(.success(data))
        } else if let error = error {
            completionHandler(.failure(error))
        } else {
            fatalError("Should be impossible to get here")
        }
    }
    task.resume()
}

이럴 때는 withUnsafeContinuationwithUnsafeThrowingContinuation 메서드를 활용하면 됩니다.

Swift의 withUnsafeContinuation 메서드는 비동기 작업의 결과를 반환하는 데 사용됩니다. 이 메서드는 비동기 작업을 수행하고, 결과를 continuation 객체를 통해 반환합니다. 예를 들어, 아래의 코드는 withUnsafeContinuation을 사용하여 비동기 작업을 수행하고 그 결과를 반환하고 있습니다.

1. withUnsafeContinuation(_:)

func withUnsafeContinuationMethod() async -> Result<Data, Error> {
    return await withUnsafeContinuation { continuation in
        download(url: URL(string: "https//www.example.url")!) { result in
            continuation.resume(returning: result)
        }
    }
}

위 코드에서 withUnsafeContinuation 메서드를 사용하여 비동기 작업을 수행하고 결과를 반환하고 있습니다. 이를 통해 기존의 비동기 작업을 async/await 메소드로 변환할 수 있습니다.

비동기 작업을 수행하는 동안 에러가 발생할 수 있는 경우에는 withUnsafeThrowingContinuation 메서드를 사용하면 됩니다. 이 메서드는 비동기 작업을 수행하고, 발생하는 에러를 처리하는 데 사용됩니다. 아래의 코드는 withUnsafeThrowingContinuation을 사용하여 비동기 작업 중 발생하는 에러를 처리하고 있습니다.

2. withUnsafeThrowingContinuation(_:)

func myAsyncThrowingMethod() async throws -> String {
    return try await withUnsafeThrowingContinuation { continuation in
        // 비동기 작업 수행
        DispatchQueue.global().async {
            if let result = performTask() {
                continuation.resume(returning: result)
            } else {
                continuation.resume(throwing: MyError.someError)
            }
        }
    }
}

위 코드에서 withUnsafeThrowingContinuation 메서드를 사용하여 에러를 던질 수 있는 비동기 작업을 처리하고 있습니다. 이를 통해 비동기 작업 중 발생하는 에러를 async/await 메소드로 처리할 수 있습니다.

이 두 메서드를 사용하면 기존의 비동기 메소드를 async/await 메소드로 쉽게 변환할 수 있습니다. 이를 통해 코드의 가독성과 유지보수성을 향상시키며, Swift의 새로운 동시성 기능을 최대한 활용할 수 있습니다. 또한, 이를 통해 Swift에서 비동기 작업을 보다 효율적으로 처리할 수 있게 됩니다.

비동기 메소드 변환의 중요성

Swift에서 비동기 메소드를 async/await 메소드로 변환하는 것은 코드의 가독성을 높이는 데 기여하며, 이는 코드 유지 보수의 중요한 요소입니다. 비동기 작업을 수행하는 코드는 복잡하고 이해하기 어려울 수 있는데, async/await 패턴을 사용하면 코드를 단순화하고 이해하기 쉽게 만들 수 있습니다.

또한, 비동기 메소드를 async/await 메소드로 변환하는 것은 Swift의 새로운 동시성 기능을 최대한 활용하는 데 도움이 됩니다. 이 기능을 활용하면, 앱의 성능을 향상시키고 사용자 경험을 개선할 수 있습니다.

변환 과정에서의 주의점

비동기 메소드를 async/await 메소드로 변환하는 과정에서는 주의가 필요합니다. withUnsafeContinuationwithUnsafeThrowingContinuation 메서드는 각각 비동기 작업의 결과를 반환하고, 발생하는 에러를 처리하는 데 사용되지만, 이 메서드들은 "unsafe"라는 이름에서 알 수 있듯이, 사용에 주의가 필요합니다.

withUnsafeContinuation

withUnsafeContinuation 메소드는 비동기 작업의 결과를 반환하는 데 사용됩니다. 이 메소드를 사용하면 비동기 작업을 수행하고, 그 결과를 continuation 객체를 통해 반환할 수 있습니다. 하지만 'unsafe'라는 이름에서 알 수 있듯이, 이 메소드는 사용에 주의가 필요합니다.

continuation 객체를 정확히 한 번만 재개해야 하며, 이를 지키지 않으면 예기치 못한 동작이나 앱의 충돌을 초래할 수 있습니다.

func withUnsafeContinuationMethod() async -> Result<Data, Error> {
    return await withUnsafeContinuation { continuation in
        download(url: URL(string: "https//www.example.url")!) { result in
            continuation.resume(returning: result)
            continuation.resume(returning: result) // 에러 발생 예정
        }
    }
}

위 코드에서는 continuation이 두 번 호출됩니다.

컴파일러는 이러한 위험에 대해 경고하지 않습니다. 그러나 빌드를 통해 코드를 실행하면

위의 이미지처럼 오류에 직면하게 됩니다.

실제 프로젝트에서 이런 오류가 발생하면, 개발자는 오류가 어디에서 발생했는지 파악하기 어렵습니다.

이런 문제를 방지하기 위해 withCheckedContinuation 메소드를 사용할 수 있습니다.

withCheckedContinuation

withCheckedContinuation 메소드는 withUnsafeContinuation의 안전한 버전으로, 동일한 기능을 제공하지만 continuation 객체가 정확히 한 번만 재개되는지 확인합니다. 이를 통해 프로그래머의 실수로 continuation 객체가 여러 번 재개되거나 재개되지 않는 경우를 방지할 수 있습니다.

func withCheckedContinuationMethod() async -> Result<Data, Error> {
    return await withCheckedContinuation { continuation in
        download(url: URL(string: "https//www.example.url")!) { result in
            continuation.resume(returning: result)
            continuation.resume(returning: result) // 에러 발생 예정
        }
    }
}

위 코드에도 마찬가지로 컴파일러는 위험에 대해 경고하지 않습니다. 그러나 빌드를 통해 코드를 실행하면

Thread 2: Fatal error: SWIFT TASK CONTINUATION MISUSE: withCheckedContinuationMethod() tried to resume its continuation more than once,

위와 같은 오류를 해당 코드에서 발생했다고 상세하게 알려줍니다.

위의 코드에서 발생한 오류에 대해 자세히 상세하게 알려주게 됩니다. 이를 통해 개발자는 디버깅을 수월하게 할 수 있게 됩니다.

물론, withCheckedContinuation 메서드는 withUnsafeContinuation에 비해 오버헤드를 발생시키므로 성능 측면에서는 withUnsafeContinuation이 더 좋을 수 있습니다. 그러나 유지보수 측면에서는 withCheckedContinuation 메서드를 사용하는 것이 좋을 수 있습니다.

마치며

withUnsafeContinuationwithUnsafeThrowingContinuation 메서드를 활용하면 기존의 비동기 메소드를 async/await 메소드로 쉽게 변환할 수 있습니다. 이를 통해 코드의 가독성과 유지보수성을 향상시키며, Swift의 새로운 동시성 기능을 최대한 활용할 수 있습니다.

이러한 방법을 통해 Swift에서 비동기 작업을 보다 효율적으로 처리할 수 있습니다.

Uploaded by

N2T