RxSwift, Network Error 처리하기
RxSwift에서 networking을 주로 Single객체로 처리한다.
이 때, error가 발생할 경우 single(.error(error))로 이벤트를 처리하면 해당 Stream이 종료된다.
이럴 경우 enum을 활용하여 error를 처리해보자!
RxSwift, Network Error 처리하기
Rx에서 Observable은 이벤트가 Complete되거나 Error가 발생될 경우 해당 Stream이 종료된다.
그래서 networking에서 error를 그대로 onError에 담아서 보내면 해당 Observable과 연결되어 있는
Stream이 전부 종료되버린다.
이렇게 되면 다음 이벤트를 처리할 수 없으므로 아래와 같이 enum을 활용하여 networking 결과를 리턴하자.
// Networking에 성공했을 경우 Success라는 객체를 리턴한다는 가정
enum NetworkResult {
case success(Success)
case error(NetworkError)
}
enum NetworkError: Int, Error {
case badRequest = 400
case authenticationFailed = 401
case notFoundException = 404
case contentLengthError = 413
case quotaExceeded = 429
case systemError = 500
case endpointError = 503
case timeout = 504
}
Networking의 결과를 NetworkResult로 반환함으로써
성공했을경우 NetworkResult.success에 // 실패했을경우 NetworkResult.error를 통해 결과를 리턴한다.
NetworkError는 Int를 rawValue로 가져 responseCode에 따라 에러종류를 반환하는 enum을 만들었다.
내부에 message() 함수를 만들어 각 case마다 에러메세지를 반환하는 방법도 있다.
그럼 이 enum을 실제 networking 코드와 연결시켜보자.
func request(parameters: Parameters, headers: HTTPHeaders) -> Single<NetworkResult> {
return Single.create { single in
let request = Alamofire.request( // Networking에는 Alamofire를 사용했다.
url,
method: .get,
parameters: parameters,
headers: headers
)
.responseData { response in
switch response.result {
case let .success(jsonData):
do {
let returnObject = try JSONDecoder().decode(Success.self, from: jsonData)
single(.success(.success(returnObject)))
// single의 success. NetworkResult.success에 담아 보낸다.
} catch let error {
single(.error(error))
// JSON 파싱에러 각자 알맞게 처리해주면 될 것 같다.
}
case let .failure(error):
if let statusCode = response.response?.statusCode {
if let networkError = NetworkError(rawValue: statusCode){
single(.success(.error(networkError)))
// single의 success. NetworkResult.error에 담아 보낸다.
}
}
}
}
return Disposables.create {
request.cancel()
}
}
}
이런식으로 networking을 처리해주면 error가 나더라고 Observable(Single)은 error 이벤트를 발생시키지 않으므로
해당 Stream이 종료되지 않고 error처리도 가능하다!
추가적으로
만약 networking이 성공했을때 리턴객체가 모두 Codable을 상속받고 있다면...?
이 Codable을 이용하여 Generic하게 만들 수 있지 않을까 라는 생각을 해보았다.
enum NetworkResult<C: Codable> {
case success(C)
case error(NetworkError)
}
struct TestA: Codable {
}
struct TestB: Codable {
}
먼저 Reture enum은 위와 같이 Codable Protocol을 제한으로 하는 Generic으로 구현하고
networking은 아래와 같이 구현했다.
func requestA(parameters: Parameters, headers: HTTPHeaders) -> Single<NetworkResult<TestA>> {
return requestGeneric(parameters: parameters, headers: headers)
}
func requestB(parameters: Parameters, headers: HTTPHeaders) -> Single<NetworkResult<TestB>> {
return requestGeneric(parameters: parameters, headers: headers)
}
func requestGeneric<C: Codable>(parameters: Parameters, headers: HTTPHeaders) -> Single<NetworkResult<C>> {
return Single.create { single in
let request = Alamofire.request( // Networking에는 Alamofire를 사용했다.
url,
method: .get,
parameters: parameters,
headers: headers
)
.responseData { response in
switch response.result {
case let .success(jsonData):
do {
let returnObject = try JSONDecoder().decode(C.self, from: jsonData)
single(.success(.success(returnObject)))
// single의 success. NetworkResult.success에 담아 보낸다.
} catch let error {
single(.error(error))
// JSON 파싱에러 각자 알맞게 처리해주면 될 것 같다.
}
case let .failure(error):
if let statusCode = response.response?.statusCode {
if let networkError = NetworkError(rawValue: statusCode){
single(.success(.error(networkError)))
// single의 success. NetworkResult.error에 담아 보낸다.
}
}
}
}
return Disposables.create {
request.cancel()
}
}
}
이런식으로 하면 Generic을 이용하여 겹치는 코드를 줄일 수 있다......!
하지만 모든 리턴객체가 Codable을 안따를수도 있고 오히려 큰 제약점이 될 수 있는 코드다.
상황에 따라 좋을 수도 있겠지만 다양하게 변할 능력은 떨어지는 코드라 장단점이 있는 것 같다 :)