[iOS/Swift] RxSwift에서 flatMap 사용 시 스트림 종료 문제와 해결 방법

2025. 4. 4. 14:54iOS/Library

728x90
반응형

🧸 문제

flatMap에서 에러 발생 시 스트림이 종료되는 현상

RxSwift에서 flatMap이나 flatMapLatest를 사용할 때, 내부 스트림에서 에러가 발생하면 전체 스트림이 종료되는 문제가 있다. 예를 들어, 아래 코드를 보자.

func transform(input: Input) -> Output {
    let responseLogin = input.buttonDidTap
        .do(onNext: { type in
            print("🟢 OAuth 실행 시작: \(type)")
        })
        .withUnretained(self)
        .flatMap { owner, type in
            owner.oauthService.execute(type: type)
                .asObservable() // Single → Observable 변환
        }
        .withUnretained(self)
        .flatMap { owner, oauthToken in
            owner.loginUseCase.execute(oauthToken: oauthToken)
                .asObservable() // Single → Observable 변환
        }
    
    return Output(resultLogin: responseLogin)
}

위 코드를 보면 oauthService.execute(type:)loginUseCase.execute(oauthToken:)에서 에러가 발생하면 스트림이 종료되므로 이후 새로운 버튼 입력이 들어와도 실행되지 않는다. 즉, 한 번 에러가 발생하면 더 이상 로그인 요청을 보낼 수 없다.

이를 해결하려면 에러 발생 시 스트림을 종료하지 않고 유지하는 방법을 적용해야 한다.

 

🧸 해결 방법 1: catch를 사용하여 스트림 유지

RxSwift의 catch 연산자를 활용하면 에러가 발생해도 스트림을 종료하지 않고 유지할 수 있다.

✔️  수정된 코드

func transform(input: Input) -> Output {
    let responseLogin = input.buttonDidTap
        .do(onNext: { type in
            print("🟢 OAuth 실행 시작: \(type)")
        })
        .withUnretained(self)
        .flatMap { owner, type in
            owner.oauthService.execute(type: type)
                .asObservable()
                .catch { error in
                    print("❌ OAuth 실패: \(error)")
                    return Observable.empty() // 에러 발생 시 빈 Observable 반환
                }
        }
        .withUnretained(self)
        .flatMap { owner, oauthToken in
            owner.loginUseCase.execute(oauthToken: oauthToken)
                .asObservable()
                .catch { error in
                    print("❌ 로그인 실패: \(error)")
                    return Observable.empty()
                }
        }
    
    return Output(resultLogin: responseLogin)
}
  • OAuthServiceLoginUseCase에서 에러가 발생해도 스트림이 종료되지 않음
  • 버튼 클릭 시 계속해서 실행됨
  • Observable.empty()를 반환하여 에러 발생 시에도 정상적인 스트림 흐름 유지

 

🧸 해결 방법 2: materialize()를 사용하여 에러를 이벤트로 처리

RxSwift의 materialize()를 사용하면 에러도 하나의 이벤트로 변환하여 스트림이 종료되지 않도록 처리할 수 있다.

✔️  수정된 코드

func transform(input: Input) -> Output {
    let responseLogin = input.buttonDidTap
        .do(onNext: { type in
            print("🟢 OAuth 실행 시작: \(type)")
        })
        .withUnretained(self)
        .flatMap { owner, type in
            owner.oauthService.execute(type: type)
                .asObservable()
                .materialize() // ✅ 에러 발생해도 스트림 종료 X
        }
        .filter { !$0.isStopEvent } // ✅ 완료 이벤트 제거
        .dematerialize() // ✅ 다시 원래 스트림으로 변환
        .withUnretained(self)
        .flatMap { owner, oauthToken in
            owner.loginUseCase.execute(oauthToken: oauthToken)
                .asObservable()
                .materialize()
        }
        .filter { !$0.isStopEvent }
        .dematerialize()
    
    return Output(resultLogin: responseLogin)
}
  • materialize()를 사용하여 에러를 이벤트로 변환 → 스트림이 종료되지 않음
  • filter { !$0.isStopEvent }를 통해 완료 이벤트 제거
  • dematerialize()다시 원래 데이터 스트림으로 변환
  • 에러가 발생해도 스트림이 유지되므로 버튼 클릭 시 계속해서 실행됨

 

 


📂 정리

RxSwift에서 flatMap을 사용할 때 내부 스트림에서 에러가 발생하면 전체 스트림이 종료되는 문제가 발생한다. 이를 방지하기 위해 두 가지 해결 방법을 적용할 수 있다.

  1. catch를 사용하여 에러 발생 시 Observable.empty() 반환 → 스트림 유지
  2. materialize()를 사용하여 에러를 이벤트로 처리 → 스트림 종료 방지

이 두 가지 방법을 활용하면 RxSwift의 스트림을 안전하게 유지하면서도 원하는 동작을 구현할 수 있다.

 

 

 

 

728x90
반응형

 

 

 

 

 

728x90
반응형

'iOS > Library' 카테고리의 다른 글