[Swift] RxSwift에 대한 고찰

유경박·2023년 8월 30일
1

PP(Procedual Programming), OOP, Reactive Programming 등 다양한 개발기법이 현존하는데 가운데 IOS개발 분야에서 Reactive Programming의 조상이자 자주 사용했던 라이브러리인 RxSwift에 대해 간략하게 쓰려고 한다.

먼저 기본적인 RxSwift흐름은 안다는 전제하에 RxSwift에서 주료사용하는 문법 및 개념을 이해하고 요약하여 글을 써보려 한다.

i)Subjects

1)PublishSubject(구독 후 이벤트 발생)

	let pbSubject = PublishSubject<String>()
    pbSubject.onNext("PublishSubject 구독하기 전1")
    pbSubject.onNext("PublishSubject 구독하기 전2")
    pbSubject
        .subscribe{
            print("1)", $0)
        }
        .disposed(by: disposeBag)
    pbSubject.onNext("PublishSubject 구독 후1")
    pbSubject.onNext("PublishSubject 구독 후2")

결과)
1) next(PublishSubject 구독 후1)
1) next(PublishSubject 구독 후2)

2)BehaviorSubject(구독하기 전 마지막 이벤트~구독 후 이벤트 발생)

    let bhSubject = BehaviorSubject<String>(value: "Initial value")
    bhSubject.onNext("BehaviorSubject 구독하기 전 1")
    bhSubject.onNext("BehaviorSubject 구독하기 전 2")
    bhSubject
        .subscribe{
            print("1)", $0)
        }
        .disposed(by: disposeBag)
    bhSubject.onNext("BehaviorSubject 구독 후 1")
    bhSubject.onNext("BehaviorSubject 구독 후 2")

결과)
1) next(BehaviorSubject 구독하기 전 2)
1) next(BehaviorSubject 구독 후 1)
1) next(BehaviorSubject 구독 후 2)

3)ReplaySubject(구독하기전 버퍼사이즈만큼 이벤트 ~ 구독후 이벤트)

    let rpSubject = ReplaySubject<String>.create(bufferSize: 2)
    rpSubject.onNext("ReplaySubject 구독하기 전 1")
    rpSubject.onNext("ReplaySubject 구독하기 전 2")
    rpSubject.onNext("ReplaySubject 구독하기 전 3")
    rpSubject
        .subscribe{
            print("1)", $0)
        }
        .disposed(by: disposeBag)
    rpSubject.onNext("ReplaySubject 구독 후 4")
    rpSubject.onNext("ReplaySubject 구독 후 5")
    rpSubject.onNext("ReplaySubject 구독 후 6")
    rpSubject.onNext("ReplaySubject 구독 후 7")

결과)
1) next(ReplaySubject 구독하기 전 2)
1) next(ReplaySubject 구독하기 전 3)
1) next(ReplaySubject 구독 후 4)
1) next(ReplaySubject 구독 후 5)
1) next(ReplaySubject 구독 후 6)
1) next(ReplaySubject 구독 후 7)

4)AsyncSubject(구독 전/후 상관없이 OnCompleted이벤트시 마지막 이벤트)

   let asyncSubject = AsyncSubject<String>()
    asyncSubject.onNext("asyncSubject 구독하기 전 1")
    asyncSubject.onNext("asyncSubject 구독하기 전 2")
    asyncSubject.onNext("asyncSubject 구독하기 전 3")
    asyncSubject
        .subscribe{
            print("1)", $0)
        }
        .disposed(by: disposeBag)
    asyncSubject.onNext("asyncSubject 구독 후 1")
    asyncSubject.onNext("asyncSubject 구독 후 2")
    asyncSubject.onNext("asyncSubject 구독 후 3")
    asyncSubject.onCompleted()

//asyncSubject는 --onCompleted이벤트를 방출했을때 마지막으로 방출된 next 이벤트를 전달(따라서 AsyncRealy 같은것은 존재 X)

결과)
1) next(asyncSubject 구독 후 3)
1) completed

총결과)
1) next(PublishSubject 구독 후1)
1) next(PublishSubject 구독 후2)
1) next(BehaviorSubject 구독하기 전 2)
1) next(BehaviorSubject 구독 후 1)
1) next(BehaviorSubject 구독 후 2)
1) next(ReplaySubject 구독하기 전 2)
1) next(ReplaySubject 구독하기 전 3)
1) next(ReplaySubject 구독 후 4)
1) next(ReplaySubject 구독 후 5)
1) next(ReplaySubject 구독 후 6)
1) next(ReplaySubject 구독 후 7)
1) next(asyncSubject 구독후 3)
1) completed

ii)Subjects vs Relay

앞서 소개한 xxxxSubjects는 RxSwift에서 제공하는 일부 클래스이며, xxxxRealy는 RxSwift의 확장 라이브러리인 RxCocoa에서 제공하는 클래스이다.

Subjects와 Realy의 주요기능은 비슷하며 Realy의 경우 onError/OnComplete 이벤트를 방출하지 않는다.
(따라서 Realy 같은 경우 AsyncRealy와 같은 클래스는 존재하지 않는다.)

iii)Observable vs Drive

Observable과 Drive모두 데이터 스트림을 나타낸다는 점에서 유사하나 차이점은 Observable의 경우 작업중인 현재 스레드에서 실행이 되지만 Driver의 경우 Main Scheduler(Rx에서 GCD 메인 스레드를 이용하기 위한 정책)에서 사용되기때문에 메인스레드에 바로 접근하여 업데이트 할수 있다.

예를 들어 다음 코드를 확인해보자.

@IBOutlet weak var btn: UIButton!
@IBOutlet weak var label: UILabel!

두개의 IBOutlet 변수가 있고 버튼을 눌렀을 때 "ZZ"라는 Text를 라벨에 업데이트 해주는 내용이다.

Driver를 이용하여 데이터 바인딩을 할 경우

btn.rx.tap.asDriver().map{"ZZ"}.drive(label.rx.text).disposed(by: disposeBag)

와 같이 간단하게 업데이트 할수가 있다.

하지만 Observable의 경우 다양한 방법으로 업데이트를 시킬수가 있는데

1).bind를 이용할경우

    btn.rx.tap
        .asObservable()
        .map { "ZZ"}
        .bind(to: label.rx.text)
        .disposed(by: disposeBag)  

2)GCD를 이용하는 경우(권장 X)

    btn.rx.tap.asObservable().map{"ZZ"}.subscribe(onNext: { [weak self]  text in
        guard let self = self else {return}
        DispatchQueue.main.async{ [weak self] in
            guard let self = self else {return}
            label.text = text
        }}).disposed(by: disposeBag)

3).observe(on)을 이용하여 명시적으로 업데이트 할 경우

    backgroundObservable.rx.tap
        .observe(on: MainScheduler.instance) // UI 업데이트는 MainScheduler에서 수행
        .bind(to: label.rx.text)
        .disposed(by: disposeBag)

등등 다양한 방법이 존재하는데 결과적으로는 모두 UI업데이트 처리를 위해 명시적으로 알려줘야 한다는 점이다.

물론 Driver의 경우 onError/onCompleted를 방출하지 않기때문에 세세한 작업을 위한다면 Observable을 이용하여 추가 처리를 해주는게 더욱 좋긴하겠지만

개인적으론 에러가 발생하면 애초에 잘못설계한거고 완료 이벤트가 방출 됐을때 추가할게 있나?라는 생각을 가지며 굳이 추가해줄 필요가 있나 싶기는하다.

주된 기본 내용은 위와 같고 연산자와 UIKit Component와의 바인딩하는 예제는 프로젝트를 만들때 따로 다룰 예정

Ps.https://github.com/QuickBirdEng/CombineRxSwiftPerformance RxSwift와 Combine 성능 비교한 깃허브 글인데.. RxSwift를 사용하는 곳에서 마이그레이션이 불가능하여 계속 써야만 하는 상황이 아닌 이상은 Combine을 쓰는것이 더 나은것 같다.

profile
으아아

2개의 댓글

comment-user-thumbnail
2023년 9월 1일

우와.. 아무것도 이해 못하겠어요... RxSwift 겁나는데요ㅠㅠ

1개의 답글