RxSwift
[RxSwift] Traits Part 2.
thoonk:
2024. 3. 8. 14:12
반응형
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
반응형