아요 개발 일기

[RxSwift] Traits [Single, Completable, Maybe] 본문

iOS/RxSwift

[RxSwift] Traits [Single, Completable, Maybe]

소진이 2023. 1. 3. 15:41

안녕하세요 소진입니당🐶

이번에는 Traits에대해 알아보도록 하겠습니다!


 

Traits 란?

Traits는 Observable을 제한적인 기능만으로 create하고 싶을 때 사용합니다.

 

 

아래 이미지처럼

Observable에서 파생된 것이 Traits이라 생각하면 됩니다!

 

 

Triats를 왜 사용할까요?

 

Observable을 생성하게 되면 구독자가 subscribe하여

onNext, onError, onCompleted등의 이벤트를 제어했었죠?

 

그런데 이런 이벤트들을 상황에 따라 굳이 모두 사용하지 않아도되는 경우가 발생할 수 있습니다.

 

만약,

저희가 그냥 Observable로부터 성공 여부만 받고 싶은 경우라면

굳이 onCompleted 이벤트도 필요 없고

onNext를 통해 인자를 전달 받기에도 조금 과할 수도 있습니다.

 

이처럼 상황에 따라서 제한적인 이벤트만 받을 수 있도록하는 것이 Traits 입니다.

 

 

Triats, 꼭 사용해야 하나요?

 

Observable로도 Triats 기능이 커버되지만,

굳이 Triats를 사용하는 진짜 목적은

 

" 내가 짜고 있는 코드의 의도를 명확히 해준다 "

 

입니다.

 

다른 사람들이 내 코드를 보게되었을 때

의도하는 바를 이해하고 코드를 읽게되므로 상호간 커뮤니케이션에 많은 도움이 되겠죠?

 

Traits의 종류

 

 RxSwift   RxCocoa 
- Single
- Completable
- Maybe

- Driver
- Signal

다양하죠??

지금은 RxSwift에 대해서 공부하고 있으니,

 Single, Completable, Maybe에 대해서 알아보도록 하겠습니다 

 


 

  Single  

 

위에서 Traits가 Observable의 파생형이라고 했었죠?

 

그 말은 즉

Traits도 엄연히 Observable이라는 뜻 입니다.

그렇다면 

Observable과 Traits는 생성하는 방식이 유사하겠죠?

 

create으로 생성하여 직접 비교해봅시다!

 

Observable Single

 

굉장히 흡사하죠??

Single 생성 코드에서 .success(), .failure() 두 가지 이벤트만 존재하는 부분만 다르네요!

 


 

이벤트 타입

 

success

 

일반 Observable의 onNext + onCompleted와 같은 역할

onNext 이벤트 방출과 동시에 onCompleted 시킴

 

 

failure

 

.failure()은 onError와 같은 역할

 


🙋🏻‍♀️

여기서 잠깐!

.success()와 .failure()는 왜 oberver.success()가 아니라

observer(.success())로 사용할까요?

 

그 이유는 Traits이 Observable의 wrapper 구조체이기 때문입니다.

즉 Observable에서 방출되는 이벤트를 기호에 맞게 감싸준 것?이라고 생각하면 됩니다.


 

아무튼 이런 Single의 특성을 이용하여 

단 한 가지의 값 또는 에러 이렇게 두 가지 형태를 보장 받을 수 있을 때 Single을 사용합니다.

 

대표적인 사용 예로는 

API request를 통해 하나의 보장된 요소나 에러를 받을때 유용하게 사용됩니다

 

func getRepo(_ repo: String) -> Single<[String: Any]> {
    return Single<[String: Any]>.create { single in
        let task = URLSession.shared.dataTask(with: URL(string: "https://api.github.com/repos/\(repo)")!) { data, _, error in
            if let error = error {
                single(.error(error))
                return
            }

            guard let data = data,
                  let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
                  let result = json as? [String: Any] else {
                single(.error(DataError.cantParseJSON))
                return
            }

            single(.success(result))
        }

        task.resume()

        return Disposables.create { task.cancel() }
    }
}

 

이렇게 하나의 json 데이터나 에러 두가지 이벤트를 보장 받을 때와 같이 말이죠

 

그리고 위 Single을 subscribe할 때는 아래와 같이 success, error에 대한 이벤트 처리만 해주면 됩니다.

 

getRepo("ReactiveX/RxSwift")
    .subscribe { event in
        switch event {
            case .success(let json):
                print("JSON: ", json)
            case .error(let error):
                print("Error: ", error)
        }
    }.disposed(by: disposeBag)

 

위와 같은 상황을 Observable로 만들었다면..

제가 만든 getRepo()라는 Observable을 다른 개발자분께서 

subscribe하고 방출되지도 않은 이벤트에 대한 처리를 해버릴 수도 있겠죠?

 


 

⚠️ 주의 사항 ⚠️

 

앞에서 

   Single  .success(), .failure() 이벤트를 방출하고

Observable  onNext, onError, onCompleted 이벤트를 방출하는점이 달랐었죠?

 

이렇게 이벤트 타입이 다르다 보니

Observable에서 Single으로 이동하는 것처럼

혼용하여 사용했을때 문제가 생기게 됩니다.

 

예를들어

저희가 asSingle() 연산자를 사용해서

Observable  Single로 시퀀스를 변환 했다고 했을때,

 

.success() 

onNext + onCompleted와 같은 존재이니

 

만약  Observable 에서 onCompleted가 발생하기 전 시점에 onNext가 없다면

Single로 변환시 에러가 발생하게 되는 것이죠.

 

 

즉, Single을 사용할 계획이라면

일관성 있게 쭉 Single로 유지하여 사용하는 것을 권장합니다!

 


 

  Completable  

 

completable은

 

" 어떤 액션이 성공했는지 "

혹은

" 에러가 발생하는지 "

 

에 대한 타입을 받고싶을 때 사용합니다.

 

 

우선 Completable과 Observable이 어떻게 다른지 코드로 살펴보겠습니다.

 

Observable Completable

 

Single보다 더 간소해보이죠??

Single의 success 이벤트와

Completable의.completed 이벤트 타입이 비슷해보였는데 뭔가 다른가 보네요!

이벤트 타입 completed, error에 대해 알아봅시다

 


 

이벤트 타입

 

completed

 

완료 됐는지 아닌지 알려주는 이벤트

 

error

onError와 같은 역할

 

 

이것만 보면 Single을 사용해도 상관 없을 것 같은데..

왜 굳이 Completed를 사용할까요?

 

예를들어

저희가 어떤 액션의 성공 여부만 받고 싶을 때 

굳이 single(.success(Element)) 이벤트를 통해 Element까지 같이 받을 필요가 없죠??

 

해당 액션이 성공 여부를 Bool 값으로 받고 싶다면 Single을 사용해야겠지만

완료 됐는지 여부만 받고 싶으면 Single을 사용할 필요가 없습니다!

 

실제로 같은 성공 여부를 전달 받는 Observable Single, Completable로 각각 만들어 봅시다.

 

Single

func saveSomething() -> Single<Bool> {
    return Single.create { single in
        // Saving something Action
        ...
        ...
        
        guard success else {
            **single(.success(false))
            single(.failure(Error))**
        }
        
        **single(.success(true))**
        
        return Disposables.create()
    }
}

 

Completable

func saveSomething() -> Completable {
    return Completable.create { completable in
        // Saving something Action
        ...
        ...
        
        guard success else {
            **completable(.error(Error))**
        }
        
        **completable(.completed)**
        
        return Disposables.create()
    }
}

 

위 코드에서 Single Completable의 가장 큰 차이를 보면

 

Single

guard success else {
    single(.success(false))
    single(.failure(Error))
}

 

Completable

guard success else {
    completable(.error(Error))
}

 

해당 에러를 처리하는 부분에 있습니다.

 

 Single 은 액션의 성공 여부를 bool로서 전달해줬었죠?

그래서 에러가 발생하게되면

single(.success(false))의 false 값뿐만아니라 failure(Error)도 동시에 전달해줍니다.

 

이렇게되면 .success(false)와 .failure() 서로의 포지션이 모호해지게됩니다

 

또한 구독하는 입장에서도 에러 처리를 false Element로 받는 곳에서 처리해야할지..

onError에서 처리해야할지 모호해지겠구요!

 

만약 에러 처리를 여러개 하게 된다면 failure()로 Error을 전달 해 줄때마다

single(.success(false))도 일일이같이 호출해 줘야되는데

그러다보면 어디선가 둘 중 하나 빼먹는 그런 불상사도 생길수도 있습니다.

 

이런 귀찮음과 불상사를 모두 미연에 방지하고자 Completable을 사용

 


 

  Maybe  

 

Maybe는 Single과 Completable의 중간 특성을 가진 Trait입니다.

 

Single Completable Maybe
▪️ .success()
▪️ .failure()
▪️ .completed
▪️ .error()
▪️ .success()
▪️ .completed()
▪️ .error()

 

위 처럼 비교해보면 Maybe가 Single, Completable이 가지고 있는

.success(), .completed(), .error()

모든 이벤트를 가지고 있는 것을 알 수 있습니다.

 

Maybe는 이벤트에서 요소를 방출시킬 수 있지만

꼭 요소를 방출 시킬 필요가 없는 경우에 유용하게 사용됩니다.

 

 

생성법도 다른 Trait과 비슷하죠?

.success, .error, .completed 모두 있네요!

 

 

다만 주의할 점은 Single의 .success() 특성도 지녔기때문에

제네릭 타입으로 방출할 요소의 타입을 지정해줘야합니다.

 


 

사용 방법

 

maybe로 Observable을 생성하고서 구독을 하게 되면

아래와같이 .successs(), .completed, error() 세가지 이벤트에 대한 처리를 할 수 있습니다.

 

코드로 살펴봅시다!

maybeObservable().subscribe(onSuccess:{ element in
    print("\(element)와 함께 completed 됐습니다.")
}, onError: { error in
    print("\(error)와 함께 completed 됐습니다.")
}, onCompleted: {
    print("아무런 요소 방출 없이 completed 됐습니다.")
}).disposed(by: disposeBag)


[실행 결과]
case1
// element와 함께 completed 됐습니다.

case2
// error와 함께 completed 됐습니다.

case3
// 아무런 요소 방출 없이 completed 됐습니다.

 

이전에 Single은 혼용해서 사용하면 에러가 발생할 수 있기때문에 일관적으로 사용해야한다했죠?

Maybe는 Observable을 .asMaybe() 연산자를 이용해서 변환시키는게 가능합니다

 

 

asMaybe()가 .completed를 통해서

onCompleted만 따로 받을 수 있기 때문에 비교적 자유롭게 오갈 수 있습니다.

 

 

글이 생각보다 길어졌습니당,,

Traits는 각 특성을 잘 이용하면 유용할 것 같네요 ㅎㅎ

다음 글에서는 

요소들을 가공하는 filtering operator에 대해서

알아보도록 하겠습니다! 🐶

 


 

마치며..

 

마지막으로 Traits을 표로 정리하고 마무리하겠습니다!

  onSuccess onError onCompleted
Single
Completable
Maybe

 

Single

오직 하나의 값 또는 에러 이렇게 두 가지 형태만 보장 해주는 Observable을 생성

 

Completable

성공 여부만 전달해주고 싶을때

 

Maybe

Single Completable 두 특성을 모두 사용하고 싶을때

 

참고 사이트

RxSwift를 스터디하는 공간 - Traits