RxSwift(8)-에러처리

2025. 5. 8. 17:40·반응형프로그래밍

에러와 Observable 존재

Observable에서 에러가 발생하면 해당 시퀀스는 즉시 종료됩니다.

// 에러 발생 시 Observable의 동작
someObservable
    .subscribe(
        onNext: { value in
            print("값: \(value)")
        },
        onError: { error in
            print("에러 발생: \(error)")
            // 이 시점에서 Observable은 종료됨
        },
        onCompleted: {
            print("완료") // 에러 발생 시 이 부분은 실행되지 않음
        }
    )

실무에서 흔히 발생하는 에러 상황은 뭐가 있을까요?

  • 네트워크 연결 실패
  • 잘못된 사용자 입력
  • 서버 에러
  • 파싱 에러 등등 ................

Catch 연산자를 활용한 에러 처리

1. catchError

func catchError(_ handler: @escaping (Error) throws -> Observable<Element>) -> Observable<Element>

에러가 발생했을 때 대체 Observable을 반환하는  클로저를 인자로 받습니다. 이를 통해 에러 유형에 따라 다른 처리를 할 수 있습니다.

apiClient.fetchData()
    .catchError { error in
        if let networkError = error as? NetworkError, networkError == .connectionLost {
            return Observable.just(cachedData) // 캐시된 데이터 반환
        } else {
            return Observable.just(emptyData) // 빈 데이터 반환
        }
    }
    .subscribe(onNext: { data in
        // UI 업데이트
    })
    .disposed(by: disposeBag)

catchError는 다음과 같은 상황에 유용합니다:

  • 특정 에러에 대해 캐시된 데이터 또는 기본값 반환
  • 다른 API 엔드포인트 또는 데이터 소스로 전환
  • 에러를 로깅하면서 대체 데이터 제공

2. CatchErrorJustReturn

func catchErrorJustReturn(_ element: Element) -> Observable<Element>

더 단순한 형태로, 어떤 에러가 발생하든 상관없이 미리 정의된 값을 반환합니다.

Retry

네트워크 불안정 등의 일시적인 문제로 인한 에러는 재시도를 통해 해결될수 있습니다.

1. retry

apiClient.fetchData()
    .retry() // 주의: 무한 재시도 가능성
    .subscribe(onNext: { data in
        // 처리 로직
    })
    .disposed(by: disposeBag)

가장 단순한 형태는 에러가 발생할때마다 무제한으로 재시도 

2. 제한된 재시도

retry (3) -> 최대 3번까지만 할께.

3. retryWhen

let maxAttempts = 4

apiClient.fetchData()
    .retryWhen { errors in
        return errors.enumerated().flatMap { (attempt, error) -> Observable<Int> in
            // 최대 시도 횟수 초과 시 에러 전달
            if attempt >= maxAttempts - 1 {
                return Observable.error(error)
            }
            
            // 1초, 3초, 5초, 10초 간격으로 재시도
            let delay = Double(attempt + 1) * 2
            print("재시도 \(attempt + 1)회 - \(delay)초 후")
            return Observable<Int>.timer(delay, scheduler: MainScheduler.instance).take(1)
        }
    }
    .subscribe(onNext: { data in
        // 처리 로직
    })
    .disposed(by: disposeBag)

retryWhen은 네트워크 요청과 같이 일시적인 실패가 예상되는 경우에 유용합니다.

고급 에러 처리 패턴?

materialize/demateriaze

materialize는 Observable의 이벤트를 Event<T>로 변환하여 에러를 포함한 모든 이벤트를 다른 Observable로 처리할 수 있게 합니다. 이를 통해 에러 처리를 더 세밀하게 제어할 수 있습니다. dematerialize는 반대로 Event<T>를 원래의 Observable로 되돌립니다.

// 특정 에러 타입을 필터링하고 다른 값으로 대체하는 패턴
apiClient.fetchData()
    .materialize()
    .map { event -> Event<Data> in
        switch event {
        case .error(let error):
            if let apiError = error as? ApiError {
                switch apiError {
                case .rateLimitExceeded:
                    // 사용 제한 초과 에러를 완료 이벤트로 변환
                    analytics.log(event: "RateLimitHandled")
                    return .completed
                    
                case .resourceNotFound:
                    // 리소스 없음 에러를 빈 데이터로 변환
                    analytics.log(event: "ResourceNotFoundHandled")
                    return .next(Data())
                    
                case .serverOverloaded:
                    // 서버 과부하는 특별 처리 없이 그대로 전파
                    return event
                    
                default:
                    // 기타 API 에러는 공통 에러로 매핑
                    return .error(AppError.apiFailure(original: apiError))
                }
            }
            return event
        default:
            return event
        }
    }
    .dematerialize()

 

에러를 변환하는데 사용할수 있다. 

enum ErrorStrategy {
    case ignore
    case replaceWithDefault
    case transformToCompletion
    case rethrow
}

func applyErrorStrategy<T>(_ strategy: ErrorStrategy, defaultValue: T? = nil) -> (Event<T>) -> Event<T> {
    return { event in
        if case .error = event {
            switch strategy {
            case .ignore:
                return .completed
            case .replaceWithDefault:
                if let value = defaultValue {
                    return .next(value)
                }
                return .completed
            case .transformToCompletion:
                return .completed
            case .rethrow:
                return event
            }
        }
        return event
    }
}

// 사용 예시
apiObservable
    .materialize()
    .map(applyErrorStrategy(.replaceWithDefault, defaultValue: Data()))
    .dematerialize()

언제쓸까?

: 특정 에러를 다른 이벤트로 변환할때, 에러를 필터링하거나 로깅 후 다른값으로 대체할때, 복잡한 에러처리 로직을 명시적으로 정의할때

flatMap과 에러 전파 제어

flatMap을 사용하면 내부 Observable의 에러를 외부 Observable로 전파하지 않도록 제어할 수 있습니다.

Observable.just(userIds)
    .flatMap { ids in
        apiClient.fetchUserData(ids)
            .catchErrorJustReturn(defaultUserData) // 내부 에러 처리
    }
    .subscribe(onNext: { userData in
        // 외부 Observable은 계속 진행
        print("유저 데이터: \(userData)")
    })
    .disposed(by: disposeBag)

에러 로깅과 사용자 알림

apiClient.fetchData()
    .catchError { error in
        // 에러 로깅
        analytics.log(event: "FetchDataError", parameters: ["error": error.localizedDescription])
        // 사용자 알림
        notificationManager.showError(message: "데이터를 불러오지 못했습니다. 다시 시도해주세요.")
        return Observable.just(emptyData)
    }
    .subscribe(onNext: { data in
        print("데이터: \(data)")
    })
    .disposed(by: disposeBag)

이런식으로 에러처리시 로깅과 사용자 알림을 추가하면 디버깅과 사용자 경험이 향상됩니다. 

'반응형프로그래밍' 카테고리의 다른 글

RxSwift(9) - Scheduler  (0) 2025.05.09
RxSwift(7)-BehaviorRelay  (0) 2025.05.06
RxSwift(6)-RxCocoa(bind, drive, DelegateProxy)  (0) 2025.05.06
RxSwift(5)-TimeBasedOperators  (0) 2025.05.04
RxSwift(4)-Combining Operators  (0) 2025.05.02
'반응형프로그래밍' 카테고리의 다른 글
  • RxSwift(9) - Scheduler
  • RxSwift(7)-BehaviorRelay
  • RxSwift(6)-RxCocoa(bind, drive, DelegateProxy)
  • RxSwift(5)-TimeBasedOperators
2료일
2료일
좌충우돌 모든것을 다 정리하려고 노력하는 J가 되려고 하는 세미개발자의 블로그입니다. 편하게 보고 가세요
  • 2료일
    GPT에게서 살아남기
    2료일
  • 전체
    오늘
    어제
    • 분류 전체보기 (121)
      • SWIFT개발 (30)
      • 알고리즘 (25)
      • Design (6)
      • ARkit (1)
      • 면접준비 (31)
      • UIkit (2)
      • Vapor-Server with swift (3)
      • 디자인패턴 (5)
      • 반응형프로그래밍 (12)
      • CS (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    비동기
    UDP
    kingfisher
    METAL
    uikit
    TCA
    TCP
    프로그래머스
    티스토리챌린지
    socket
    combine
    Python
    Dependency
    SwiftUI
    알고리즘
    Protocol
    operators
    ios
    Vapor
    shader
    image
    오블완
    Swift
    filter
    HIG
    CoreLocation
    actor
    RxSwift
    Coremotion
    cs
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
2료일
RxSwift(8)-에러처리
상단으로

티스토리툴바