모야는 참 편하다.
TargetType를 채택하려면 저 다섯가지를 꼭 작성해야한다고 친히 Fix가 뜨고 나는 안에 값만 잘 넣어주면 된다.
OpenWeatherMap의 One Call API를 사용해 날씨 정보를 받아오려면 먼저 네트워크 요청을 어떻게 구성해야 할지 고민을 했다.
난 Moya 라이브러리를 이용해서 구조적으로 API를 관리하는 방법을 선택했는데,
TargetType 프로토콜을 구현해서 API 엔드포인트를 정의하는 과정을 해보면서 각 프로퍼티가 어떤 역할을 하는지 자세히 공부해봤다.
import Foundation
import Moya
enum WeatherAPI {
case oneCall(lat: Double, // 위도
lon: Double, // 경도
exclude: String, // 제외할 항목
units: String) // 단위 (metric = 온도 섭씨 (°C))
}
extension WeatherAPI: TargetType {
var baseURL: URL {
return URL(string: "https://api.openweathermap.org/data/3.0")!
}
var path: String {
switch self {
case .oneCall:
return "/onecall"
}
}
var method: Moya.Method {
return .get
}
var task: Moya.Task {
switch self {
case let .oneCall(lat, lon, exclude, units):
let parameters: [String: Any] = [
"lat": lat,
"lon": lon,
"exclude": exclude,
"units": units,
"appid": "8ef68fb1d27fb92f55802750b90d54f7"
]
return .requestParameters(parameters: parameters, encoding: URLEncoding.default)
}
}
var headers: [String : String]? {
["Content-Type": "application/json"]
}
}
enum WeatherAPI
의 역할enum
은 여러 가지 상태나 케이스를 타입 안전하게 표현하기에 적합한데, 내 코드에서는 One Call API만 사용하기 때문에 oneCall(lat:lon:exclude:units:)
라는 하나의 케이스만 정의해봤다.
lat
: 위도 lon
: 경도 exclude
: 응답에서 제외할 항목 units
: 온도 및 풍속 등의 단위
exclude
파라미터응답에서 특정 기상 정보를 제외하고 싶을 때 사용하는 건데, 예를 들어
"minutely"
를 제외하면 1분 단위 예보 데이터가 응답에서 빠진다고 한다.
exclude="minutely,hourly"
처럼 쉼표로 구분하면 여러 항목을 동시에 뺄 수 있기도 한데 이렇게 불필요한 데이터를 제거해야 트래픽 절감과 파싱 비용 감소에 도움이 된다고 한다.
units
파라미터온도, 풍속 등의 단위를 결정하는 것.
"metric"
이면 섭씨(°C)와 m/s,"imperial"
이면 화씨(°F)와 mph 단위를 사용하는데 만약 기본값(standard
)으로 할 경우에는 켈빈 단위를 사용한다고 한다.
이렇게 열거형의 연관값을 통해 이 API가 필요한 매개변수를 선언해줬는데 이 덕분에 잘못된 타입을 넘겨주는 실수를 컴파일 단계에서 막을 수가 있다.
.
.
TargetType
프로토콜 구현그리고 Moya를 사용하려면 TargetType
프로토콜을 준수해야 하는데, 주요 프로퍼티로는 baseURL
, path
, method
, task
, headers
등이 있다.
baseURL
https://api.openweathermap.org/data/3.0
을 사용했고 URL(string:)
이 옵셔널을 반환하기 때문에 강제 언래핑을 사용했다.path
"/onecall"
로 지정을 했고method
.get
을 반환했다.task
.oneCall(lat, lon, exclude, units)
케이스를 분기해주고 [String: Any]
딕셔너리를 만들어 lat
, lon
, exclude
, units
, appid
(API Key) 등을 넣어준다. .requestParameters(parameters:encoding:)
로 반환해주면, GET 방식이라서 URL 쿼리 스트링으로 반환이 된다.headers
["Content-Type": "application/json"]
을 설정했다. BookApp
에서는 카카오 API처럼 헤더 기반 인증(Authorization: KakaoAK ...
)이 필요했었어서 이 프로퍼티에 그 키를 넣어주면 됐었다. OpenWeatherMap
은 쿼리 파라미터에 appid
를 넣는 방식을 사용하기 때문에 별도의 인증 헤더를 넣을 필요가 없었다.타입 안전성
가독성 및 유지보수 용이
TargetType
프로토콜을 통해 API 호출 정보를 하나의 구조로 관리한다. 중복 제거
API별 요구 사항 대응
Authorization
)로 키를 보내야 하고, appid
)로 키를 보내도록 요구할 수 있다. switch self
구문 안에서 각각의 로직을 자유롭게 구현할 수 있다.순차적으로 해보니까 Moya와 TargetType을 사용하면 각 엔드포인트별로 필요한 정보를 체계적으로 관리할 수 있다는게 좀 더 와닿은게, enum으로 API를 정의하고, task
메서드에서 파라미터와 인코딩 방식을 결정하니 코드의 가독성과 유지보수성이 대폭 향상된 느낌이 뭔지 느껴졌다.
OpenWeatherMap의 One Call API뿐 아니라, 저번에 했던 bookApp을 했을 때처럼 다른 서비스를 연동허게 될 때도 비슷한 방식으로 접근할 수 있는거고
API마다 인증이나 파라미터 요구가 다를 수는 있겠지만 TargetType 프로토콜을 구현하는 패턴은 대부분 동일하기 때문에 혹시라도 여러 API를 동시에 연동해야 하는 상황이 생긴다면 이 구조를 사용할 경우 혼동을 줄이고 팀원분들과 역할 분담도 조금이나마 명확하게 할 수 있을 것 같다.