apple/RxSwift, ReactorKit

RxSwift Ch12. Beginning RxCocoa

lgvv 2021. 8. 12. 14:14

RxSwift Ch12. Beginning RxCocoa

애플의 CocoaTouch를 처리하는 RxCocoa에 대해서 알아보자.

근데, 작성하다가 파일 한번 날아감 ㅠㅠㅠㅠㅠㅠ

 

 

코드 분석

주석으로 설명

searchCityName.rx.text // textField와 연결
    .filter { ($0 ?? "").count > 0 } // 입력값이 존재하면 넘어가게끔
    .flatMapLatest { text in // 가장 마지막에 구독된 스트림의 값만으로 갱신
        return ApiController.shared.currentWeather(for: text ?? "Error")
            .catchErrorJustReturn(ApiController.Weather.empty) // 에러나면 빈 배열 리턴
    }
    .observeOn(MainScheduler.instance) // 메인 스케줄러 지정
    .subscribe(onNext: { data in // 구독
        self.tempLabel.text = "\(data.temperature)℃"
        self.iconLabel.text = data.icon
        self.humidityLabel.text = "\(data.humidity)%"
        self.cityNameLabel.text = data.cityName
    })
    .disposed(by: disposeBag) // dismiss될 때 구독 날리게끔 + 리소스 낭비 막음

 

 

// 1 - rxcocoa text field
let search = searchCityName.rx.text
    .filter { ($0 ?? "").count > 0 }
    .flatMapLatest { text in
        return ApiController.shared.currentWeather(for: text ?? "Error")
            .catchErrorJustReturn(ApiController.Weather.empty)
    }
    .share(replay: 1, scope: .whileConnected) // 스트림 하나를 공유하게끔
    .observeOn(MainScheduler.instance) 

// 2 - rxcocoa binding
search.map { "\($0.temperature)℃" } // $0(data).temperature 의 의미
    .bind(to: tempLabel.rx.text) // tempLabel에 rx형식으로 붙이기 
    .disposed(by: disposeBag)

search.map { "\($0.humidity)%" }
    .bind(to: humidityLabel.rx.text)
    .disposed(by: disposeBag)

search.map { $0.cityName }
    .bind(to: cityNameLabel.rx.text)
    .disposed(by: disposeBag)

search.map { $0.icon }
    .bind(to: iconLabel.rx.text)
    .disposed(by: disposeBag)

 

 

 

RxCocoa Trait( driver, asDriver )를 통한 코드 개선

Trait이란?  

  • Trait은 UI와 함께 사용되도록 독점적으로 생성된 observable 항목의 특수한 구현을 제공한다. Trait는 직관적이고 작성하기 쉬운 코드를 작성하는데 도움이 되는 Observable의 특수 클래스다. (특히 UI 작업할 때)
  • Traits 는 observable sequence 객체가 인터페이스 영역과 소통할 수 있도록 도와준다.

Trait특징

  • Trait은 에러를 방출할 수 없다.
  • Trait은 메인 스케줄러에서 관찰한다 (observing)
  • Trait은 메인 스케줄러에서 구독한다 (subscribing)
    • 즉, Trait을 사용하면 .observeOn(MainSchduler.instance) 호출을 생략 가능하다
  • Trait은 부수작용을 공유한다 (side effect)

Trait 프레임워크의 주 요소

  • ControlProperty : 데이터와 UI를 연결할때 rx extension을 통해서 사용한 적이 있음
  • ControlEvent : 텍스트필드에서 글자를 입력할때 return을 누르는 것과 같이 UI구성요소에서의 확실한 이벤트를 듣기 위해 사용한다. ControlEvent는 구성요소가 UIControlEvents를 현재 상태에 계속 두고 사용할 때 사용 가능하다.
  • Driver : 에러를 방출하지 않는 특별한 observable이다. 모든 과정은 UI변경이 background 쓰레드에서 이뤄지는 것을 방지하기 위해 메인 쓰레드에서 이뤄진다.

Trait 마무리하며...

  • Trait를 억지로 사용할 필요는 없다. 처음에는 순수히 Subject나 Observable만 쓰는 것도 나쁘지 않다. 하지만 만약 컴파일링 중에 또는 UI와 관련된 어떤 예정된 법칙을 체크하고 싶을 때, Trait은 아주 강력한 기능을 제공하며 시간 절약에도 좋다.
  • Trait을 사용하면 .observeOn(MainScheduler.instance) 호출에 대해 잊어버려도 좋다. 또한 background 쓰레드에서 UI를 생성할 필요도 없다.
  • Driver와 ControlProperty가 지금은 어려워 보일 수 있다. 천천히 하나씩 확인해보자.
// 1 - rxcocoa text field with Trait
let search = searchCityName.rx.controlEvent(.editingDidEndOnExit).asObservable()
// 유저의 편집이 끝났을 때만 호출
    .map { self.searchCityName.text }
    .flatMap { text in
        return ApiController.shared.currentWeather(for: text ?? "Error")
    }
    .asDriver(onErrorJustReturn: ApiController.Weather.empty)
    // 구독하는거라고 생각 asDriver = observeOn + subscribe


// 2 - rxcocoa binding with Trait
search.map { "\($0.temperature)℃" }
    .drive(tempLabel.rx.text) // asDriver에서 bind -> drive로 변경 
    .disposed(by: disposeBag)

search.map { "\($0.humidity)%" }
    .drive(humidityLabel.rx.text)
    .disposed(by: disposeBag)

search.map { $0.cityName }
    .drive(cityNameLabel.rx.text)
    .disposed(by: disposeBag)

search.map { $0.icon }
    .drive(iconLabel.rx.text)
    .disposed(by: disposeBag)


코드의 특징 및 설명

  • controlEvent(.editingDidEndOnExit) 메소드를 통해 텍스트필드가 입력이 완료되었을 때만 observable 을 생성한다.
  • 입력이 완료된 후 클릭하는 것이기 때문에 입력값은 반드시 유효하다고 생각할 수 있다. (빈칸을 걸러내는 부분을 고려하지 않아도된다.)
  • 유저가 Search 버튼을 탭 했을때만 날씨 정보를 받으므로, 불필요한 네트워크 요청이 없기때문에 기존 flatMapLatest 에서 catchErrorJustReturn() 호출을 삭제할 수 있다.
  • controlEvent(.editingDidEndOnExit) 메소드를 통해 텍스트필드가 입력이 완료되었을 때만 observable 을 생성한다.
  • 입력이 완료된 후 클릭하는 것이기 때문에 입력값은 반드시 유효하다고 생각할 수 있다. (빈칸을 걸러내는 부분을 고려하지 않아도된다.)
  • 유저가 Search 버튼을 탭 했을때만 날씨 정보를 받으므로, 불필요한 네트워크 요청이 없기때문에 기존 flatMapLatest 에서 catchErrorJustReturn() 호출을 삭제할 수 있다.

 

RxCocoa 와 dispose 하기 - disposeBag 과 순환참조에 대하여

  • 기존 코드에 MainViewController 에 disposeBag 을 두고, 모든 구독의 dispose 를 관리하고 있다.
  • 클로저 내부에서 weak, unowned 키워드를 사용하지 않는 이유는?
    • 단일 뷰 컨트롤러이고, Main VC 에서는 앱이 구동되는 동안 항상 스클니에 띄워져있기 때문에 메모리 낭비를 고려할 필요가 없기 때문이다.

추가적인 설명에 대한 사진

 

RxCocoa 의 extension 살펴보기

간단하게 사진으로 대체

두 가지만 간단히 보자

 

 

 

'apple > RxSwift, ReactorKit' 카테고리의 다른 글

RxSwift 06 RxDataSources  (0) 2021.08.19
Ch13. Intermediate RxCocoa  (0) 2021.08.12
🐉 RxSwift 4Hour - Step3(Rx)  (0) 2021.07.18
🐉 RxSwift + MVVM (TableView) 코드1  (2) 2021.07.15
🐉 RxSwift(Relay와 subject)  (0) 2021.07.12