RxSwift

RxSwift/RxCocoa - Traits

GREEN.1229 2025. 2. 17. 08:49

안녕하세요. 그린입니다 🍏

이번 포스팅에서는 RxSwift의 Traits에 대해 정리해 보겠습니다 🙋🏻


Traits?

우선 RxSwift에서 Observable은 매우 유용한 스트림이지만, 때로는 특정한 액션을 강제하고 싶을때가 있을 수 있습니다.

Traits이 이런 특정한 패턴을 따르는 Observable의 변형으로 그 역할을 해줍니다.

즉, Observable로는 원래 onNext, onError, onCompleted 등을 다 처리했다면 Traits은 필요한것만 처리할 수 있죠.

 

RxSwift와 RxCocoa에서는 다음과 같은 Traits들을 제공합니다 😃

 

1️⃣ Single

2️⃣ Completable

3️⃣ Maybe

4️⃣ Driver

5️⃣ Signal

6️⃣ ControlProperty

7️⃣ ControlEvent

 

각 Traits는 특정한 목적과 규칙을 가지고 있고, 일반 Observable보다 더욱 직관적으로 동작을 이해할 수 있도록 도와줘요.

 

그럼 다음으로 Traits의 장/단점을 한번 살펴볼까요?

 


Traits의 장/단점

장점

1️⃣ 특정한 사용 목적을 가지기에 코드 가독성 향상

2️⃣ Observable보다 예측 가능한 동작 보장

3️⃣ UI 바인딩을 위한 Driver / Signal을 제공해 안전한 UI 업데이트 가능

4️⃣ Single / Completable /  Maybe를 활용해 비동기 작업의 결과를 명확히 표현 가능

 

단점

1️⃣ 일반적인 Observable보다는 유연성 감소

2️⃣ Driver / Signal은 내부적으로 share()를 사용하기에 과도하게 남용된다면 불필요 메모리 사용 증가

 

그럼 이렇게 간략히 Traits을 알아봤으니 Traits들의 종류에 대해 하나씩 정리해보겠습니다 😁

 


Single

Single은 하나의 요소 혹은 에러만 방출하고 완료되는 스트림입니다.

일반적으로 네트워크 요청이나 데이터베이스 쿼리 등에서 사용됩니다.

 

특징으로는 다음과 같아요.

 

1️⃣ success(요소) 혹은 error를 방출한 후 종료

2️⃣ UI에서 Driver처럼 주로 비동기 작업에 적합

3️⃣ subscribe(onSuccess:onError:) 메서드 제공

 

사용은 이렇습니다.

 

let single = Single<String>.create { single in
    let success = Bool.random()
    if success {
        single(.success("데이터 로드 성공"))
    } else {
        single(.failure(NSError(domain: "NetworkError", code: -1, userInfo: nil)))
    }
    return Disposables.create()
}

single.subscribe(
    onSuccess: { print("성공: \($0)") },
    onFailure: { print("실패: \($0.localizedDescription)") }
)

 

다음으로 Completable에 대해 정리해볼께요.

 


Completable

Completable은 값을 방출하지 않고 완료 혹은 에러를 방출하는 스트림입니다.

주로 데이터 저장과 같은 작업에 사용됩니다.

 

특징으로는 다음과 같아요.

 

1️⃣ 요소 없이 completed 혹은 error 발생

2️⃣ subscribe(onCompleted:onError:) 메서드 제공

 

사용은 이러합니다.

 

let completable = Completable.create { completable in
    let success = Bool.random()
    if success {
        completable(.completed)
    } else {
        completable(.error(NSError(domain: "DBError", code: -1, userInfo: nil)))
    }
    return Disposables.create()
}

completable.subscribe(
    onCompleted: { print("저장 성공") },
    onError: { print("저장 실패: \($0.localizedDescription)") }
)

 

RxSwift의 마지막 Traits인 Maybe에 대해 보겠습니다.

 


Maybe

Maybe는 Single과 Completable이 결합된 형태로, 값 하나를 방출할 수도 있고, 아무것도 방출하지 않을 수도 있으며 에러를 발생 시킬 수도 있습니다.

 

특징으로는 다음과 같아요.

 

1️⃣ success (요소) / completed / error 중 하나를 방출

2️⃣ 선택적인 결과가 필요한 작업에서 유용

 

사용은 이러합니다.

 

let maybe = Maybe<String>.create { maybe in
    let random = Int.random(in: 0...2)
    if random == 0 {
        maybe(.success("성공 데이터"))
    } else if random == 1 {
        maybe(.completed)
    } else {
        maybe(.error(NSError(domain: "UnknownError", code: -1, userInfo: nil)))
    }
    return Disposables.create()
}

maybe.subscribe(
    onSuccess: { print("성공: \($0)") },
    onCompleted: { print("완료됨") },
    onError: { print("에러 발생: \($0.localizedDescription)") }
)

 

다음으로 그러면 RxCocoa의 Traits들에 대해 알아볼께요!

 


Driver

Driver는 UI 바인딩을 안전하게 처리하도록 설계된 Observable의 변형입니다.

 

특징으로는 다음과 같아요.

 

1️⃣ MainScheduler에서 실행

2️⃣ UI 바인딩을 위한 onErrorJustReturn을 제공해 에러 발생 시 기본값 설정 가능

3️⃣ share(replay: 1, scope: .whileConnected)로 공유됨 

 

다음과 같이 사용됩니다.

 

let textField = UITextField()
let label = UILabel()

let textDriver = textField.rx.text.orEmpty.asDriver(onErrorJustReturn: "")

textDriver
    .drive(label.rx.text)
    .disposed(by: disposeBag)
    
    
let tableView = UITableView()
let items = Observable.just(["Apple", "Banana", "Cherry"])
    .asDriver(onErrorJustReturn: [])

items
    .drive(tableView.rx.items(cellIdentifier: "Cell")) { _, item, cell in
        cell.textLabel?.text = item
    }
    .disposed(by: disposeBag)

 

이전 Driver에 대해 다룰때 익숙히 봤을거라 참고하셔도 좋습니다!

 

 

RxSwift - Driver & BehaviorRelay

안녕하세요. 그린입니다 🍏이번 포스팅에서는 Driver와 BehaviorRelay에 대해 알아보며 연관 관계에 대해 정리해보겠습니다 🙋🏻 그전에, BehaviorRelay에 대해 알고 있는것이 더 편리해요.   RxSwift -

green1229.tistory.com

 

그럼 다음으로 Signal에 대해 보겠습니다.

 


Signal

Signal은 이벤트 기반 UI 처리를 위해 설계된 Observable의 변형이에요.

 

특징으로는 다음과 같아요.

 

1️⃣ MainScheduler에서 실행됨

2️⃣ 에러 방출 X

3️⃣ 최신 이벤트 유지하지 않음

4️⃣ UI 이벤트 처리에 적합

 

사용으로는 다음과 같습니다.

 

let buttonTap = PublishRelay<Void>()
let signal = buttonTap.asSignal()

signal.emit(onNext: { print("버튼 클릭됨") })

buttonTap.accept(())

 

다음으로 ControlProperty를 정리해볼께요.

 


ControlProperty

ControlProperty는 UI 컨트롤의 상태를 나타내는 Observable입니다.

UITextField.text와 같은 바인딩 가능한 속성에 사용됩니다.

 

예시로는 다음과 같아요.

 

let textField = UITextField()
let disposeBag = DisposeBag()

textField.rx.text.orEmpty
    .subscribe(onNext: { text in
        print("입력된 텍스트: \(text)")
    })
    .disposed(by: disposeBag)

 

어디서 많이 보던거죠?

우리가 예시들을 설명할때 UI 컴포넌트에 rx를 붙여서 사용했잖아요.

거기서 rx.text로 접근한거 기억하시죠?

그거처럼 여기서 rx 네임스페이스로 사용되는게 바로 이겁니다.

error 이벤트를 발생시키지 않아요.

 

다음으로 그럼 ControlEvent를 볼께요.

 


ControlEvent

ControlEvent는 UI 이벤트인 버튼 클릭, 스크롤 이벤트 등을 나타내는 Observable입니다.

 

예시를 볼까요?

 

let button = UIButton()

button.rx.tap
    .subscribe(onNext: {
        print("버튼 클릭됨")
    })
    .disposed(by: disposeBag)

 

이렇게 rx의 tap이 ControlEvent Traits이라고 볼 수 있어요.

쉽죠!?

 


이처럼 Traits에 대해 알아봤는데요.

Observale을 Traits으로 변환하는 메서드들이 존재합니다.

 

간단히 예시만 볼까요?

 

let observable = Observable.just("Hello")
let single = observable.asSingle()
let driver = observable.asDriver(onErrorJustReturn: "Default")

 

이런식으로 간단하게 변환할 수 있어요.

 

그리고, 위 Driver와 Signal을 보다 그럼 어떤걸 사용해야하지? 라고 생각이 들 수 있어서 정리해볼께요.

 


Driver vs Signal

공통점으로는 다음과 같아요.

 

1️⃣ UI 바인딩을 위함

2️⃣ MainScheduler에서 실행

3️⃣ 공유 상태를 가짐

 

차이점으론 이러합니다.

 

특징 Driver Signal
에러 처리 onErrorJustReturn 필요 자동 무시
이벤트 유지 최신 이벤트 유지 최신 이벤트 유지 X

 

주로 대부분의 UI 바인딩은 Driver를 사용하는것이 적절하겠고, 얼럿과 같은 단발성에서는 Signal을 사용해도 될것 같아요.

 

즉, 텍스트 필드와 그런 UI 바인딩에선 Driver, 버튼을 클릭해 얼럿이 뜨거나 알림을 주거나 하는 UI 이벤트 처리에선 Signal이 적절할것 같습니다.

 

그럼 이렇게 Traits들에 대해 정리해봤는데요.

 

마지막으로, 표로 어떤 Traits들을 상황에 맞게 써야하는지 정리해볼께요!

 


When to use Traits?

Traits 상황
Single 네트워크 요청, DB 조회
Completable 데이터 저장, 파일 삭제
Maybe 데이터 존재 여부가 불확실한 작업
Driver UI 바인딩 (텍스트 필드, 버튼 등)
Signal UI 이벤트 처리 (버튼 클릭, 알림 등)
ControlProperty UI 요소의 상태 바인딩
ControlEvent UI 이벤트 트리거

 

이렇게 어느정도 구분으로 각자 사용할 상황을 정리해볼 수 있습니다.

 


마무리

Rx에서의 Traits은 Observable을 더욱 명확한 용도로 사용할 수 있도록 도와주는것 같아요.

Traits을 상황에 맞게 잘 활용한다면 코드의 가독성과 의도가 확실해질거라 생각합니다!

 


레퍼런스

 

RxSwift/Documentation/Traits.md at main · ReactiveX/RxSwift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com