티스토리 뷰

반응형

 

 

 

오늘은 RxSwift의 Observable, 연산자 사용 및 구독을 이용하여 API를 요청, 응답 데이터를 View에 렌더링하는 과정을 연습해보도록 하겠습니다.

 오늘 예제 앱 개발에 사용되는 API는 아래 사이트의 open Weather API를 사용합니다. 사이트 가입 후, 개인 API Key를 발급 받아서 API 요청 URL 주소로 사용하시길 바랍니다.

 

Weather API - OpenWeatherMap

Please, sign up to use our fast and easy-to-work weather APIs. As a start to use OpenWeather products, we recommend our One Call API 3.0. For more functionality, please consider our products, which are included in professional collections.

openweathermap.org

 

 


URL Extension 구현 (Weather API URL 정의)
URL+Extension.swift

URL extension에 Weather open API URL을 반환하는 메서드를 정의합니다. 특정 city를 인자로 받아서 그에 맞는 날씨 정보를 제공하는 API입니다. 이어서, 저희가 사용할 날씨 API 응답 JSON 포맷 예시를 보겠습니다.

 

 


Weather API Model 구성 방법
URLRequest+Extension.swift

아래는 open Weather API요청에 대한 JSON 응답 예시입니다. 다양한 필드가 내려오지만, 저희는 이중에 "main" 필드와 그 하위의 "temp", "humidity" 필드를 사용하도록 하겠습니다.

{
  "coord": {
    "lon": 16.7458,
    "lat": 53.6059
  },
  "weather": [
    {
      "id": 802,
      "main": "Clouds",
      "description": "scattered clouds",
      "icon": "03d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 55.72,
    "feels_like": 53.53,
    "temp_min": 55.72,
    "temp_max": 55.72,
    "pressure": 1002,
    "humidity": 54,
    "sea_level": 1002,
    "grnd_level": 984
  },
  "visibility": 10000,
  "wind": {
    "speed": 7.47,
    "deg": 251,
    "gust": 10.11
  },
  "clouds": {
    "all": 43
  },
  "dt": 1664446750,
  "sys": {
    "country": "PL",
    "sunrise": 1664427037,
    "sunset": 1664469366
  },
  "timezone": 7200,
  "id": 3083826,
  "name": "Korea",
  "cod": 200
}

 

 


WeatherResult.swift

위에서 보았던 JSON 응답 포맷에 맞게 모델을 구성한 모습입니다. 에러가 발생했을때 placeholder로 나타날 데이터를 extension WeatherResult 블럭 내 empty 타입 프로퍼티로 정의하고 있습니다. 또한 본 모델을 get 요청으로 받아서 디코딩하는 목적으로만 사용하므로 Decodable 프로토콜을 채택해서 사용합니다.

 

 

13 ~ 15행) 먼저 제네릭 리스트를 갖고 있는 Resource 구조체를 정의합니다. 특정 API 모델 요청을 위한 URL를 넣어서 생성하는 구조체로, 제네릭으로 정의되었기에 다양한 타입에 대한 모델로 사용될 수 있습니다.

19행) load 타입 메서드를 정의합니다. Decodable 프로토콜을 준수하는 제네릭 타입의 Resource를 인자로 받아서  Observable<T> 타입으로 반환하며, View에서 사용될 예정입니다.
20행) 단일 이벤트를 방출할때 사용하는 Just 연산자로 resource의 url: URL 변수를 down stream에 내려줍니다.
21 ~ 23행) flatMap은 특정 이벤트를 Observable 타입으로 변환할때 사용합니다. rx로 접근해서 사용할 수 있는 RxCocoa의 래퍼 메서드(URLSession.shared.rx.response(request:)를 down stream에 내려줍니다. Observable<(response: HTTPURLResponse, data: Data)>의 튜플 이벤트 Observable 타입으로 down stream에 내려갑니다.
* Combine의 flatMap은 특정 이벤트 값을 Publisher타입으로 변환할때 사용됩니다.

24 ~31행) map 연산자를 통해 response, data가 있는 튜플 타입을 받아서 디코딩을 시작합니다. http 상태코드가 정상일 경우 디코딩, 아닐 경우 error를 던지고 있습니다.
32행) 외부에서는 모든 stream의 연산 동작을 알 필요가 없습니다. 그저 외부에서는 결과값만 사용하면 되기 때문입니다. 이렇게 Observable의 마지막 연산 결과를 사용할때 asObservable()를 사용하여 Observable<T> 타입으로 변환할 수 있습니다.
* RxSwift의 asObservable()은 Combine으로 치면, eraseToAnyPublisher()와 유사한 기능의 래퍼 메서드라고 볼 수 있을 것 같습니다.

이렇게 특정 url에 대한 API를 요청하고 이에 맞는 모델을 디코딩한 결과를 방출하는 Observable 반환 메서드를 구성할 수 있었습니다.
이어서 이제 View를 구성해본 뒤, 위에서 구성한 Observable 이벤트와 바인딩해보도록 하겠습니다.

 

 


View 구성 후 Observable 구독, 바인딩하기
ViewController.swift

먼저 city이름을 입력할 UITextField, 입력한 city 이름에 맞는 온도, 습도를 보여줄 UILabel 2개를 추가해주세요. 또한 Obseravble 구독, 바인딩 정보(Disposables)를 담아서 메모리 관리를 할 disposeBag을 선언해주세요.

 

displayWeather(_:) 메서드 구현

API 응답을 받았을 경우, UILabel에 해당 정보를 뿌려주는 메서드입니다. 값이 있으면 온도, 습도를 설정하고, 없을경우 그에 맞는 텍스트를 지정해주면 됩니다.

 

fetchWeather(by city:) 메서드 구현

city String을 API 요청에 알맞도록 인코딩을 한 후, Resource<WeatherResult> 구조체를 생성했습니다. 

69 ~ 77행) URLRequest+Extension.swift에서 구현한 load<T>(resource:) 메서드를 사용합니다. url에 맞는 API요청을 하는데, 에러가 발생하면 placeholder로서 WeatherResult.empty 객체를 반환합니다. 이후 구독을 통해 이벤트 결과를 UI에 적용하는 displayWeather 메서드를 호출하는 모습입니다. 여기서도 구독이 발생했으니 구독정보 관리를 위해 disposed(by:)코드를 적용해줍니다.

* UI를 다룰때에는 메인스레드에서 동작해야하므로 DispatchQueue.main을 사용하고 있는데, 이외의 방법으로 observe(on:) 메서드나 asDriver()를 사용하는 방법도 있으니 아래에서 추가적으로 다뤄보겠습니다. 이어서 viewDidLoad 오버라이딩 메서드를 가보겠습니다.

 

 


Weather API 데이터 UI에 적용방법
1) subscribe 구독을 통한 UI 업데이트 방법

28 ~ 29행) city를 입력받을 textField의 편집이 끝났을때의 이벤트 감지를 위해 RxCocoa 래퍼 메서드인 rx.controlEvent(.editingDidEndOnExit)를 사용해줍니다. 이는 ControlEvent<()> 타입을 반환하는데 이를 Reactive하게 처리하기 위해 asObservable()을 사용하여 Observable<()> 타입으로 변환해줍니다.
30행) map 연산자를 통해 텍스트필드 편집이 끝났을때의 텍스트필드에 입력된 텍스트를 매핑해서 down stream에 내려줍니다.
31 ~ 41행) 텍스트필드의 입력 텍스트가 비어있지 않다면, fetchWeather(by:) 메서드를 실행해서 API를 요청한 후, 응답을 View에 뿌려줍니다. 현재 구독정보에 대한 메모리 관리를 위해 마지막에는 disposed(by: disposeBag)을 지정해줍니다.

subscribe, DispatchQueue.main을 사용하여 메인스레드에서 구독 이벤트를 UI에 적용해볼 수 있었는데요. 다른 방법으로는 observe(on:), bind(to:)를 통해 UI와 이벤트를 바인딩 하는 방법을 알아보겠습니다.

 

 

 


2) bind(to:)를 통해 UI 이벤트 바인딩방법

71행) URLRequest.load(resource:) 를 통해 먼저 API를 요청해 응답 결과를 Observable 타입으로 받아서 down stream에 내려줍니다.
72행) observe(on:)에 MainScheduler.instance를 지정해서 메인스레드에서 동작하도록 하고 있습니다. UI에 데이터를 적용해야하기 에 메인스레드로 설정하고 있습니다.
73행) catchAnyReturn 언산자는 에러 발생시 placeholder를 반화할  수 있도록 해줍니다. 에러 발생 시, WeatherResult.empty 객체를 반환합니다.
75 ~ 77행) searchObservable에서 방출하는 Observable<Weather>에서  필요한 온도 데이터만 맵핑하고 있습니다. 이후 bind(to:)를 사용해서 온도를 표출할 라벨과 바인딩합니다. bind(to:)를 사용했을때에도 subscribe 메서드 사용때처럼 disposed(by:) 코드를 추가해주어 메모리를 관리합니다.
79 ~ 81행) 75 ~ 77행)에서는 온도 정보라 라벨을 바인딩한다면, 이번에는 습도 정보를 UILabel과 바인딩하는 모습이며 동작방식을 동일합니다. 이처럼 Main thread에서 UI를 업데이트해주는 동일한 동작을 다른 방식으로 구현해볼 수 있었습니다.

subscribe, observe(on:), bind(to:) 이외로도 drive 연산자를 통해 UI와 이벤트를 바인딩할수도 있습니다. drive를 사용하면 메인스레드에서의 동작을 보장하므로, UI 바인딩을 할때 용이합니다. 세번째 방법도 보겠습니다.

 

 


3) asDriver, drive 연산자를 사용해서 UI 이벤트 바인딩방법

72 ~ 77행) driver를 사용하면 별도의 스레드 지정 코드를 작성할 필요가 없습니다. 그 자체로 메인스레드에서 동작하기 때문입니다. 위 코드에는 별도의 연산자, retry가 들어갔는데 이는 에러가 발생했을때 재시도를 할 수 있도록 해주는 연산자입니다. 예를들어 retry(3)은 3번 재시도를 할 수 있게 해줍니다. drive를 사용하기 위해서는 먼저, asDriver()를 통해 drive()를 사용할 수 있게 만들어야 합니다. 이때 onErrorJustReturn을 통해 에러발생 시 placeholder를 지정할 수 있습니다.

78 ~ 83행) 앞서 2번 방식, bind(to:)와 다르게 observe(on:) 연산자 없이 drive 연산자만 지정하고 있습니다. 이렇게 UI 이벤트 바인딩 시 drive연산자를 사용하면 보다 간결하게, 메인스레드에서 안전하게 동작하도록 할 수 있다는 장점이 있습니다.

* drive, bind(to:) 모두 내부적으로 subscribe를 사용하고 있습니다.

 

 


오늘은 RxSwift, RxCocoa를 사용하여 Weather open API 데이터를 요청하고 응답을 UI에 랜더링 하는 과정을 다뤄봤습니다.

 그 과정 속에서 subscribe, bind(to:), drive 등 다양한 구독, 바인딩 방식을 통해서 동일한 앱 동작을 구현해볼 수 있었습니다. 제가 작성한 내용과 관련하여 질문이나 의견이 있으시거나, 제가 놓치고 있는 부분이 있다면 언제든 댓글 달아주세요.

추가로, Weather API 앱 동작 영상 예시 링크를 아래 첨부합니다. 즐거운 코딩 되시길 바랍니다. 감사합니다!

https://tv.kakao.com/channel/3287479/cliplink/432368721

 

 

 

 

 

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함