티스토리 뷰

반응형

 

 

안녕하세요. 오늘은 iOS의 Combine framework에서 제공하는 Publisher와 다양한 이벤트 연산자를 통해 비동기 이벤트를 처리하는 방법을 알아보겠습니다. Open Weather API를 요청하여 응답을 받는 과정을 진행해보려고 합니다.
텍스트필드에 city 명을 입력해서 온도, 습도 데이터를 받아오는 예제입니다. (debounce 연산자를 사용해서 타이핑이 끝난 이후 결과값을 1회 받아옵니다.)

* Combine은 이벤트 처리 연산자들을 사용하여 비동기 이벤트를 처리할 수 있도록 해주는 프레임워크입니다.

 


사용할 Weather Open API

무료로 사용 가능한 Open API를 사용하여 Combine을 통한 비동기 이벤트 처리를 할 생각입니다. 아래 사이트에서 가입 후, 개인 API키를 발급받아서 사용하세요.

 

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

요청받을 JSON 데이터 포맷은 아래와 같습니다. 다양한 필드를 가진 JSON 데이터이지만, 저희는 이중 "main" 필드만 선택적으로 사용을 하겠습니다.

{
  "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
}

 

 


JSON 모델 구성하기

앞서 말했듯이, open API로부터 수신할 JSON 데이터 중 main 필드의 일부 데이터만 사용을 하려고 합니다. main 필드의 하위 필드인 temp, humidity를 사용하기 위해 모델을 구성했습니다. 위 데이터 모델은 get으로 받아서 디코딩만 하므로, Decodable 프로토콜을 채택해주었습니다.
 (디코딩 뿐만 아니라 인코딩을 해서 post 요청을 해야한 다면, Codable을 채택해서 사용해야 인코딩이 가능합니다. 만약 인코딩만 필요하다면 Encodable을 채택해서 사용할 수도 있습니다.)

 


API 요청 객체 구성하기

먼저, Constants 파일을 생성하고 API 요청에 필요한 URL 주소를 정의합니다. 무료 open API이지만, 사이트에 가입해서 고유 키를 발급받아서 사용해야합니다. 또한, 검색하고자 하는 city 명을 인자로 받아서 URL 주소에 추가해주어야합니다.

 

 

WebService 파일을 추가 후, API 요청 객체, WebService를 생성했습니다. 다음에 구현할 View에서 입력받은 city 명을 인자로 받아서 URL을 생성 후, URLSession의 dataTaskPublisher를 사용합니다.

17행) 인자로 받은 url에 대한 dataTask가 성공하면, data, response 데이터를 담은 튜플(Tuple) 데이터를 down stream에 전달합니다.
18행) 튜플데이터를 그대로 받아 처리할 수도 있지만, data 만 사용하고자 할때, KeyPath를 사용해서 data만 downstream으로 내릴 수있습니다.
19행) 앞서 구성한 데이터모델 포맷에 맞게 디코딩을 진행합니다. 
20행) 디코딩 후 JSON 데이터의 main 필드만 매핑하여 downstream으로 내립니다..
21행) 에러 발생 시, 더이상 추가적인 이벤트를 publish 하지 않는 Empty Publisher를 반환할 수 있습니다. 이 경우에는 Weather값이 nil로 떨어집니다.
22행) 요청받은 API 데이터를 UI에 적용하기 위해서는 메인스레드에서 동작해야합니다. 따라서 메인스레드에서 down stream이 동작하도록 receive(on: RunLoop.main)을 사용할 수 있습니다.
23행) 지금까지 처리한 결과물의 진행과정은 굳이 외부에 공개될 필요성이 없습니다. 외부에서는 그저 최종 결과물만 받아서 사용하면 됩니다. 이때 사용가능한 것이 eraseToAnyPublisher()입니다.
 이 메서드를 사용하면 지금까지 처리된 stream 타입을 AnyPublisher로 감싸서 최종 결과물로 사용할 수 있도록 합니다. 보통 최종 결과물로 사용할때 AnyPublisher로 변환하여 사용이 가능하며, 따라서 해당 메서드의 반환타입도 AnyPublisher<Weather, Error> 인 것을 보실 수 있습니다.

이제 WebService의 메서드를 통해 처리된 데이터를 View에 적용 해보겠습니다.

 


View 구성 및 API 데이터 적용 하기 

프로젝트의 기본 ViewController: UIViewController에 뷰를 구성합니다. 텍스트필드에 city 명을 입력하면 관련 정보가 나오도록 할 생각입니다. 위와 같이 UILabel, UITextField를 추가한 후, 앞서 정의한 WebService 인스턴스를 추가합니다. viewDidLoad에 사용되는 setupPublisher 메서드 구현은 아래에서 진행됩니다.

21행) cancellable은 RxSwift의 Disposable과 유사한 개념으로 구독정보를 담아서 메모리 관리를 하기위해 사용됩니다.

 

 

마지막으로 setupPublishers 구현 부입니다.


44행) cityTextField의 텍스트 변화를 감지하여 변화할때마다 이벤트를 방출하는 publisher를 생성합니다. 
46 ~ 49행) 앞서 정의한 publisher에 compactMap를 사용하고 있습니다. compactMap은 nil이 아닌 값들로 매핑을 진행하는 연산자입니다. 텍스트필드가 변화할 때마다 변화한 텍스트를 .urlHostAllowed 타입으로 인코딩 해주고 있습니다. 이렇게 인코딩 된 값은 downstream에서 city 인자로 넘겨져 사용됩니다.
50행) debounce 는 특정 시간 동안 변화가 없을 경우, downstream으로 넘어가도록 해주는 연산자입니다. 500 millisecond 기준으로 변화가 없을 경우, 메인스레드 상에서 downstream로 내려갑니다. debounce는 한글자 한글자 칠때마다 API가 호출되지 않고, 타이핑이 끝난 뒤 단 한번 API를 호출하여 비효율적인 API 호출을 방지하고자 할 때 사용할 수 있습니다.(RxSwift에서도 동일)
51 ~ 59행) flatMap은 upstream에서 내려온 이벤트 값을 Publisher 타입으로 변환하여 내려줄때 사용할 수 있습니다. 여기에서 이전에 구현한 webService의 fetchWeather 메서드를 사용하고 있습니다. 에러 발생 시, Empty Publisher를 반환하여 성공할 경우, 그에 맞는 temp(온도) 문자열을 downstream으로 내려줍니다. 
60행) 마지막으로 assign 구독 연산자를 통해서 temperatureLabel의 text에 결과값을 전달하고 있습니다. assign 이외에도 sink 를 통해서 값을 적용할 수도 있습니다.

 

 


아래에 지금까지 구현한 예제 앱 동작 예시 영상 링크 올려드립니다.
내용에 대해 의견이나 질문 있으시면 언제든 댓글 부탁드립니다.
즐거운 코딩, 즐거운 combine 사용 되세요 감사합니다!

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

 

 

 

 

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함