https://moonggi-dev-story.tistory.com/49 글에 이어서 ReactorKit 에 대해 적도록 하겠습니다.
Http 통신중에 발생할수 있는 오류에 대해 ReactorKit으로 처리할수 있는 방법을 적겠습니다.
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .http:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
setHttp().map{Mutation.setResult($0)},
Observable.just(Mutation.setLoading(false))
])
}
}
해당 프로세스는 로딩(true) -> Http 통신 완료후 -> 로딩(false) 와 같은 순서를 보이고 있고
Http 통신중에 오류가 발생하면 중간에 끊깁니다.
setHttp 상에서 오류를 처리하여 해당 프로세스를 진행 할수 있습니다. 어케보면 RxSwift 지식이 있다면 처리 할수 있는 문제입니다.
1. Mutate
enum Mutation {
case setLoading(Bool)
case setResult(String)
case onError(ConnectError)
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .http:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
setHttp().map{Mutation.setResult($0)}.catchAndReturn(Mutation.onError(ConnectError.APIException)),
Observable.just(Mutation.setLoading(false))
])
}
}
setHttp 함수상에서 onError 가 발생하면 다음과 같이 동작하며, concat의 동작도 계속 이어갈수 있습니다
에러 발생 관련 메세지 받는방법은 State 선언하여 해당 변수를 구독 합니다.
2. State
struct State {
var isLoading : Bool = false
var text : String = ""
var errorResult = ConnectError.DefaultError
}
func reduce(state: State, mutation: Mutation) -> State {
switch mutation {
case let .setLoading(val):
var state = state
state.isLoading = val
return state
case let.setResult(val):
var state = state
state.text = val
return state
case let.onError(error):
var state = state
state.errorResult = error
return state
}
}
3. View
reactor.state.map{
$0.errorResult
}.distinctUntilChanged()
.filter{ $0 != .DefaultError }
.subscribe(onNext : {
error in
print("에러 발생 :\(error)")
}).disposed(by: disposeBag)
기본값인 DefaultError 를 제외하고 filter 상에서 걸리게 처리하였다.
reactorkit 특성상 구독을 걸어 놓으면 errorResult 값이 바뀌지 않아도 다른 state 값이 바뀌면 그전 값도 같이 내려오게 된다.
그래서 항상 distinctUntilChanged() 를 걸어놓는다.
하지만 로그인 alert 팝업이라던가, 똑같은 값이 두번 내려올경우에 구독을 못받을수가 있습니다.
해당 내용은 임의의 값을 추가하여 다음과 같이 처리 할수 있습니다.
4. ReactorValue
struct ReactorValue<T> {
var revision : Int = 0
var value : T?
}
5. State
struct State {
var revision = 0
var isLoading : Bool = false
var text = ReactorValue<String>(revision: 0, value: nil)
var errorResult = ReactorValue<ConnectError>(revision: 0, value: ConnectError.DefaultError)
}
func reduce(state: State, mutation: Mutation) -> State {
switch mutation {
case let .setLoading(val):
var state = state
state.isLoading = val
return state
case let.setResult(val):
var state = state
state.revision = state.revision + 1
state.text = ReactorValue(revision: state.revision, value: val)
return state
case let.onError(error):
var state = state
state.revision = state.revision + 1
state.errorResult = ReactorValue(revision: state.revision, value: error)
return state
}
}
6. View
reactor.state.map{ $0.text }
.distinctUntilChanged {
$0.revision == $1.revision
}
.subscribe(onNext : {
value in
self.resultTextView.text = "\(value.value)"
}).disposed(by: disposeBag)
reactor.state.map{
$0.errorResult
}.filter {
$0.value != .DefaultError
}
.distinctUntilChanged {
$0.revision == $1.revision
}
.subscribe(onNext : {
error in
print("에러 발생 :\(error.value)")
}).disposed(by: disposeBag)
다음과 같이 revision 값을 추가하여 실제 값을 reduce 할때만 추가해서 보냅니다.
view 에서 구독 받을땐 해당 기존 revision 값과 현재 값을 비교하여 틀릴 경우에만 값을 적용 합니다.
참고 : https://github.com/ReactorKit/ReactorKit/issues/34
'IOS' 카테고리의 다른 글
Swift - Property Wrapper (0) | 2021.08.06 |
---|---|
Swift - Reactive Programming (Rx) (0) | 2021.08.02 |
Swift - iOS 면접 질문 리스트 (0) | 2021.07.18 |
Swift - UICollectionView + Paging (0) | 2021.07.13 |
Swift - SceneDelegate (0) | 2021.07.10 |