어떤 이벤트들은 onCompleted나 onError에 걸려도 종료되지 않기를 바랄수도 있다. 예컨대 UI관련이라면 어쩌다 오류가 생긴다고 해도
구독 스트림이 해제되지 않는 것이 유저에게 어색하게 보이지 않을 것이다. 내부에서 오류가 생겼다고 해서 구독이 없어져버리면 그 다음에
오류가 아닌 제대로된 데이터가 들어오더라도 업데이트가 안될 것이 아닌가...!!
또, 어떤 경우는 값이 오는건 별로 중요하지 않지만 종료되는 순간만 중요할 수도 있다! 이런 여러가지 상황에 대응하기 위해서 특별한 Observable을 만들었는데 그게 바로 Traits다.
어떤 경우에는 딱 한번만 이벤트를 받고싶고, 그 이후에는 뭐가 들어오든 안받고 싶을 수 있다. 마치 API를 통한 HTTP 요청처럼! 그리고 Side effect를 공유하지 않아야 할 때 Single을 사용한다.
func getRepo(_ repo: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
let task = URLSession.shared.dataTask(with: URL(string: "https://api.github.com/repos/\(repo)")!) { data, _, error in
if let error = error {
single(.failure(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
let result = json as? [String: Any] else {
single(.failure(DataError.cantParseJSON))
return
}
single(.success(result))
}
task.resume()
return Disposables.create { task.cancel() }
}
}
위는 api요청을 해서 error가 뜨면 single(.failure(error))를 통해 에러를 전달하고, 성공시에는 result를 전달하는 Single을 구현한 것이다.
그렇다면 구독하는 입장에서는 아래와 같이 사용하면 된다!
사용법 1 (Switch 구문 사용하기)
getRepo("ReactiveX/RxSwift")
.subscribe { event in
switch event {
case .success(let json):
print("JSON: ", json)
case .failure(let error):
print("Error: ", error)
}
}
.disposed(by: disposeBag)
사용법 2 (제공되는 파라미터 사용하기)
getRepo("ReactiveX/RxSwift")
.subscribe(onSuccess: { json in
print("JSON: ", json)
},
onError: { error in
print("Error: ", error)
})
.disposed(by: disposeBag)
부릉부릉~ 드라이버는 왜 이름이 드라이버일까? 공식문서에 친절한 설명이 있는데, 구동한다고 할 때 쓰는 그 Drive를 의미하는 것 같다.
드라이버는 RxCocoa에서 제공하는 Trait중 하나이다. Driver하면 바로 UI를 떠올릴 수 있는데 그 이유는 다음과 같다.
share(replay: 1, scope: .whileConnected)
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)
위의 코드가 하려는 일을 정리하면 다음과 같다.
그런데 이 코드에는 몇가지 문제점이 있다!!
이 코드를 조금 개선해 보면 아래와 같다.
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)
위 코드에서는 메인스케줄러에서 동작하도록 .observeOn을 사용해줬고, error시에 빈 배열을 리턴하도록해서 dispose되지 않도록 처리했다.
또한 share(replay: 1)을 통해서 새로 구독을 하더라도 이전 값을 1번은 사용하도록 하였다.
자 그럼 아래 코드를 보자
let results = query.rx.text.asDriver() // asDriver()를 통해 시퀀스를 Driver로 바꾸어 주었다.
.throttle(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn: []) // 에러때 무엇을 던져줄지 설정한다.
}
results
.map { "\($0.count)" }
.drive(resultCount.rx.text) // driver의 경우에는 bind(to:)대신 drive()를 사용한다.
.disposed(by: disposeBag)
results
.drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)
여기서 변화한 점은 asDriver() 부분이다. 단순히 Driver의 ControlProperty를 몇가지 더 쓸수있는 것 이외에 변한 것은 없다.
두번째로 변한 것은 .asDriver(onErrorJustReturn: [])부분이다.
그리고 Driver로 바꿔주니 share(replay:1) 부분을 생략해도 된다!
옵저버블은 언제나 Driver로 변환해서 사용할 수 있는데,
만약에 그냥 옵저버블로 만들어서 Driver로 리턴하고 싶다면 아래와 같이 작성할 수 있다.
let safeSequence = xs
.observeOn(MainScheduler.instance) // observe events on main scheduler
.catchErrorJustReturn(onErrorJustReturn) // can't error out
.share(replay: 1, scope: .whileConnected) // side effects sharing
return Driver(raw: safeSequence) // wrap it up
시그널은 Driver랑 다른점이 딱 하나 있는데, 바로 마지막 element를 절대 replay하지 않는 다는 점이다. 자동으로 share되지 않는 Driver 인 셈이다.
공식 다큐멘테이션을 참고하였음 (링크)
[Swift/iOS] 촬영한 이미지 돌아감 현상의 원인과 해결방법 (+카메라앱) (0) | 2023.02.13 |
---|---|
[Medium/번역] Swift 로 Money Type 만들기 (0) | 2022.10.06 |
[RxSwift] Observable 생성자 (create, just) (1) | 2022.09.05 |
[Swift] 스위프트의 집단 자료형에 대해서 알아보자! - 배열과 집합 (0) | 2022.05.31 |
[Swift] 고차함수 map, filter, reduce를 완전 쉽게 알아보자 (2) | 2022.05.17 |