본문 바로가기
RxSwift

[RxSwift] Traits Part 2.

by thoonk: 2024. 3. 8.
반응형

RxCocoa Traits

  • RxSwift Traits 기반으로 UI 개발을 위해 특화된 RxCocoa 에서 사용하는 Wrapper 구조체

Driver

UI 계층에서 반응형 코드를 작성하는 직관적인 방법을 제공하거나 앱을 Driving 하는 데이터 스트림을 모델링하는 데 사용됨

  • error 을 방출하지 않음, 시퀀스 오류가 발생하더라도 앱은 input 에 대해서 반응하지 않음.
  • Main Scheduler
  • 사이드 이펙트를 공유함(share(replay: 1, scope: .whileConnected))
    • 기본 값으로 가장 최근에 방출했던 아이템 값을 방출함.
    • 구독할 때마다 새로운 Observable 이 생성되지 않고 공유할 수 있음.
    • .while Connected: Subscriber 가 0개가 되고 disposed 되면 대상이 비워짐.

→ Observable sequence 는 error 발생하지 않으며 메인스레드에서 관찰되고 이는 UI 요소에 바인딩하기 안전함을 의미

 

.asDriver()

  • ControlProperty trait을 Driver trait으로 변환(Wrapping)
query.rx.text.asDriver()

 

.asDriver(onErrorJustReturn:)

  • 에러 발생 시 모든 에러에 대해 동일한 값을 반환
.asDriver(onErrorJustReturn: [])

 

.asDriver(onErrorDriveWith:)

  • 에러 발생 시 대체 Driver로 변환하는 데 사용됨.
let networkRequestObservable = Observable<Data>.create { observer in
  // 네트워크 요청 수행
  
  if let data = response?.data {
    observer.onNext(data)
  } else {
    observer.onError(NSError(domain: "com.example", code: 1, userInfo: nil))
  }
}

let errorDriver = Observable.just("네트워크 오류 발생").asDriver(onErrorJustReturn: "")

let dataDriver = networkRequestObservable.asDriver(onErrorDriveWith: errorDriver)

dataDriver.drive(onNext: { data in
  // 네트워크 요청 결과 처리
})

 

.asDriver(onErrorRecover:)

  • Driver 가 에러를 받았을 때, 복구할 함수를 사용하여 Driver로 변환하는 데 사용됨
let networkRequestObservable = Observable<Data>.create { observer in
  // 네트워크 요청 수행
  
  if let data = response?.data {
    observer.onNext(data)
  } else {
    observer.onError(NSError(domain: "com.example", code: 1, userInfo: nil))
  }
}

let dataDriver = networkRequestObservable
  .asDriver(onErrorRecover: { error -> Driver<NetworkError> in 
			return Driver<NetworkError>.just(NetworkError.dataError(error))
	})

dataDriver.drive(onNext: { data in
  // 네트워크 요청 결과 처리
})

Driver를 쓰지 않는 상황

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
    }

results
    .map { "\\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\\(result)"
    }
    .disposed(by: disposeBag)
  • query 텍스트필드에 유저가 넣는 글자에 throttle 을 걸어 매번 검색되게 하지 않고 검색한 결과를 result 에 저장함.
  • 검색된 결과를 resultCount 레이블에 바인딩
  • 검색된 결과를 결과 테이블 뷰 셀의 textLabel 에 넣기

→ 이 상황에서 문제점

  • fetchAutoCompleteItems 옵저버블이 error 를 방출하면 모든 UI가 응답을 안 하고 아무리 입력을 해도 안 나올 것
  • fetchAutoCompleteItems 가 메인 스레드에서 동작하지 않으면 앱 충돌이 날 수 도 있음
  • 두 개의 UI 요소에서 바인딩되어 있어 두 번 요청을 하게 됨.

Driver 없이 개선된 상황

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .observeOn(MainScheduler.instance)  // 메인스케줄러에서 동작
            .catchErrorJustReturn([])           // 에러 발생 시 빈 배열을 반환
    }
    .share(replay: 1)                           // 요청한 결과값을 공유하여 두 번 요청되지 않도록 함

results
    .map { "\\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\\(result)"
    }
    .disposed(by: disposeBag)
  • 메인 스레드에서 동작하도록 하며, 에러 발생 시 빈 배열 리턴하도록 처리
  • .share(replay: 1) 을 통해 새로 구독을 하더라도 이전 값을 1번 사용하도록 처리

Driver 사용 코드

let results = query.rx.text.asDriver()        // asDriver()를 통해 시퀀스 타입 변경
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .asDriver(onErrorJustReturn: [])  // 에러 발생 시 빈 배열 반환
    }

results
    .map { "\\($0.count)" }
    .drive(resultCount.rx.text)               // driver는 drive()를 사용하여 처리
    .disposed(by: disposeBag)             

results
    .drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\\(result)"
    }
    .disposed(by: disposeBag)
  • 같은 동작을 하는 코드임에도 더 간결해지고 메인스레드에서 작동하므로 크래시로부터 안전해진 코드를 볼 수 있음.

Signal

Driver 와 유사하지만 구독 시 최신 이벤트를 replay 하지 않고 subscribers 는 시퀀스의 computational resources 를 공유함. (시퀀스를 drive 와 동일하게 공유)

→ 반응형 방시으로 명령형 이벤트를 모델링하는 빌더 패턴으로 간주할 수 있음.

  • 에러를 방출하지 않음
  • Main Scheduler
  • computational resources 공유 (share(replay: 1, scope: .whileConnected))
  • 구독 시 요소를 replay 하지 않음.
public typealias Signal<Element> = SharedSequence<SignalSharingStrategy, Element>

public struct SignalSharingStrategy: SharingStrategyProtocol {
    public static var scheduler: SchedulerType { SharingScheduler.make() }

    public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
        source.share(scope: .whileConnected)
    }
}

 

부족한 점 피드백해주시면 감사합니다 :)

Ref.

https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md

반응형

'RxSwift' 카테고리의 다른 글

[RxSwift] Deferred  (0) 2024.03.11
[RxSwift] Relay  (0) 2024.03.11
[RxSwift] Traits Part 1.  (0) 2024.03.08
[RxSwift] Error Handling  (0) 2024.03.08
[RxSwift] Time Based Operators  (0) 2022.07.18

댓글