Skip to content

Concurrency 방식으로의 전환 #6

@camosss

Description

@camosss

Continuation

  • 동기/콜백 기반 코드를 async/await로 연결해 주는 어댑터
    • CheckedContinuation (런타임에 오류 체크하는 객체)

      • withCheckedContinuation
      • withCheckedThrowingContinuation
    • UnsafeContinuation (런타임에 오류 체크하지 않는 객체, 퍼포먼스 좋음)

      • withUnsafeContinuation
      • withUnsafeThrowingContinuation

continuation.resume

중단된 async 작업을 재개시키는 트리거 (1회 호출)

  • resume() -> Void 반환

  • resume(returning: value) -> 성공 값 전달

  • resume(throwing: error) -> 에러 전달 (Error 타입)

  • resume(with: result) -> Result<Success, Error> 그대로 전달


콜백 기반 API → async/await (단발성 결과)

  • 기존 completion을 그대로 감싼 async 함수로 전환

  • 성공만 있는 경우 → CheckedContinuation<Success?, Never>

// 기존: completion 기반
func fetchUserWithCompletion(completion: @escaping (User) -> Void) {
    ...
    completion(user)
}

// 전환: async (성공만, 에러 미포함)
func fetchUser() async -> User {
    await withCheckedContinuation { 
        (continuation: CheckedContinuation<User, Never>) in

        fetchUserWithCompletion { user in
            continuation.resume(returning: user)
        }
    }
}

에러 있는 콜백 → async/await (throws)

  • Result 에러 콜백을 async throws로 전환

  • 성공/실패가 있는 경우

    • withCheckedThrowingContinuation로 전환

    • resume(throwing:)의 파라미터는 Error 타입이어야 함

    • 이미 Result로 받는다면 resume(with: result)로 바로 전달

// 기존: Result 기반 콜백
func fetchUserWithCompletion(completion: @escaping (Result<User, Error>) -> Void) {
    ... 
    if let user {
        completion(.success(user)) 
    } else {
        completion(.failure(Error.failed))
    }
}

// 전환: async throws
func fetchUser() async throws -> User {
    try await withCheckedThrowingContinuation { 
        (continuation: CheckedContinuation<User, Error>) in

        fetchUserWithCompletion { result in

            // switch로 success/throwing 분기
            switch result {
            case .success(let user):
                continuation.resume(returning: user) // 성공 값 전달
            case .failure(let error):
                continuation.resume(throwing: error) // 에러 전달
            }

            // (또는) 성공/실패를 그대로 전달
            continuation.resume(with: result)          
        }
    }
}

UnsafeContinuation

  • 성능 최적화가 절대적으로 필요할 때만 사용

  • 체크가 없으므로 resume 정확히 한 번 호출을 스스로 보장 필요

func fetchUser() async throws -> User {
    try await withUnsafeThrowingContinuation { 
        (continuation: UnsafeContinuation<User, Error>) in

        fetchUserWithCompletion { result in
            // 체크 없음: 중복/누락 가능
            continuation.resume(with: result)          
        }
    }
}

델리게이트 기반 API → async/await (단발성 이벤트)

기존 (델리게이트 방식)

  • 델리게이트 메서드가 불리는 시점에 콜백 내부에서 값이 전달됨

  • 값을 저장해두고 필요할 때 조회

    • 이 경우 언제 값이 세팅될지 확실하지 않으므로, 흐름 제어가 분산
var currentLocation: CLLocation?

func locationManager(
    _ manager: CLLocationManager,
    didUpdateLocations locations: [CLLocation]
) {
    guard let location = locations.last else { return }

    // 즉시 delegate에서 처리
    self.currentLocation = location 
}

// 다른 곳에서 나중에 접근 (저장된 위치 사용)
if let location = currentLocation {
    print("\(location)")
}

async/await + Continuation

  • await로 값을 기다릴 수 있는 비동기 함수로 전환

    • Continuation 생성 메서드로 결과값 활용

    • 비동기적으로 생기는 값을 await 기다렸다가 받아서 활용

  • 델리게이트에서 resume으로 재개 -> 흐름 제어가 동기 코드처럼 작성

var currentContinuation: CheckedContinuation<CLLocation, Error>?

// await로 위치값을 기다릴 수 있는 비동기 함수로 전환
func getCurrentLocation() async throws -> CLLocation {
    try await withCheckedThrowingContinuation { continuation in

        // (1) continuation 외부 저장
        currentContinuation = continuation              
        locationManager.requestLocation()
    }
}

func locationManager(
    _ manager: CLLocationManager,
    didUpdateLocations locations: [CLLocation]
) {
    guard let location = locations.last else { return }

    // (2) 값 반환 시점에 재개
    currentContinuation?.resume(returning: location)   

    // (3) 재개 후 해제
    currentContinuation = nil                         
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions