ErrorHandlingInPractice

RxSwift: Reactive Programming with Swift | raywenderlich.com

image

Catching errors

    private var cache = [String: Weather]()

    override func viewDidLoad() {
        let textSearch = searchInput.flatMap { text in
          return ApiController.shared.currentWeather(city: text)
            .do(onNext: { [weak self] data in
              self?.cache[text] = data
            })
            .catchError { error in
                return Observable.just(self.cache[text] ?? .empty)
            }
        }

スクリーンショット 2022-09-25 13 55 54


Retrying on error

        
        let textSearch = searchInput.flatMap { text in
          return ApiController.shared.currentWeather(city: text)
            .do(onNext: { [weak self] data in
              self?.cache[text] = data
            })
            .retry(3)
            .catchError { [weak self] error in
              return Observable.just(self?.cache[text] ?? .empty)
            }
        }

スクリーンショット 2022-09-25 13 55 54


Advanced retries

        let maxAttempts = 4
        
        let textSearch = searchInput.flatMap { text in
            return ApiController.shared.currentWeather(city: text)
                .do(onNext: { [weak self] data in
                    self?.cache[text] = data
                }).retryWhen { e in
                    return e.enumerated().flatMap { attempt, error -> Observable<Int> in
                        print("== retrying after \(attempt + 1) seconds ==")
                        if attempt >= maxAttempts - 1 {
                          return Observable.error(error)
                        }
                        return Observable<Int>.timer(.seconds(attempt + 1),
                                                     scheduler: MainScheduler.instance)
                                              .take(1)
                    }
                }
                .catchError { [weak self] error in
                    return Observable.just(self?.cache[text] ?? .empty)
                }
        }
== retrying after 1 seconds ==
... network ...
== retrying after 2 seconds ==
... network ...
== retrying after 3 seconds ==
... network ...

スクリーンショット 2022-09-25 13 55 54


Creating custom errors

スクリーンショット 2022-09-25 13 55 54

ApiController

  private func buildRequest(method: String = "GET", pathComponent: String, params: [(String, String)]) -> Observable<Data> {
    let request: Observable<URLRequest> = Observable.create { observer in
      let url = self.baseURL.appendingPathComponent(pathComponent)
      var request = URLRequest(url: url)
      let keyQueryItem = URLQueryItem(name: "appid", value: try? self.apiKey.value())
      let unitsQueryItem = URLQueryItem(name: "units", value: "metric")
      let urlComponents = NSURLComponents(url: url, resolvingAgainstBaseURL: true)!

      if method == "GET" {
        var queryItems = params.map { URLQueryItem(name: $0.0, value: $0.1) }
        queryItems.append(keyQueryItem)
        queryItems.append(unitsQueryItem)
        urlComponents.queryItems = queryItems
      } else {
        urlComponents.queryItems = [keyQueryItem, unitsQueryItem]

        let jsonData = try! JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
        request.httpBody = jsonData
      }

      request.url = urlComponents.url!
      request.httpMethod = method

      request.setValue("application/json", forHTTPHeaderField: "Content-Type")

      observer.onNext(request)
      observer.onCompleted()

      return Disposables.create()
    }

    let session = URLSession.shared
    return request.flatMap { request in
        return session.rx.response(request: request)
          .map { response, data in
          switch response.statusCode {
          case 200 ..< 300:
            return data
          case 400 ..< 500:
            throw ApiError.cityNotFound
          default:
            throw ApiError.serverFailure
          }
        }

    }
  }

ViewController

         let textSearch = searchInput.flatMap { text in
            return ApiController.shared.currentWeather(city: text)
                .do(onNext: { [weak self] data in
                    self?.cache[text] = data
                },
                onError: { error in
                    DispatchQueue.main.async { [weak self] in
                        guard let self = self else { return }
                        self.showError(error: error)
                    }
                })
          .catchError { [weak self] error in
                    return Observable.just(self?.cache[text] ?? .empty)
                }
   }             
   
   private func showError(error e: Error) {
      guard let e = e as? ApiController.ApiError else {
        InfoView.showIn(viewController: self, message: "An error occurred")
        return
      }

      switch e {
      case .cityNotFound:
        InfoView.showIn(viewController: self, message: "City Name is invalid")
      case .serverFailure:
        InfoView.showIn(viewController: self, message: "Server error")
      }
    }