iOS에서 REST api를 다루기 위한 방법에 대해서 스터디를 한다.
사실 iOS framework 자체에서도 URL Loading하는 라이브러리를 제공하긴 하지만
제공하는 기능들을 사용하는 방법들이 생각만큼이나 간단하지 않고 예쁘지 않다.
그래서 많은 iOS 개발자들이 기본 iOS framework에서 제공하는 기능을 확장하고 더 편리한 도구들을 제공하는 Alamofire를 사용하는 것이 일반적이다.
Alamofire/Alamofire
Alamofire가 제공하는 방법은 굉장히 직관적이며 편리하다.
간단한 Request 예시를 보자
import Alamofire
func request() {
AF.request(
//요청 url
BASE + url,
//post, delete, update, get 메소드
method: .post,
//제공하는 parameter
parameters: ["someKey": "someValue"],
//parameter 인코딩 방식
endcoder: JSONParameterEncoder.default
//헤더
headers: [:]
.responseString { response in
print(response)
}
}
위처럼 간결하게 Request를 정의해 요청을 보낼 수 있으며, 콜백도 쉽게 받을 수 있다.
콜백을 받는 방법도 아래와 같이 여러가지 방식이 있다.
// 기본적인 문자열로 받기
.responseString { reponse in }
// JSON 객체로 받기
.responseJSON{ data in }
// Swift4에서 제공되기 시작한 기능으로
// response가 미리 객체로 파싱되어 내려와 더 편하게 사용할 수 있다.
// Decodable로 바로 받기
.responseDecodable{ (response: AFDataResponse<D>) in
do {
//아래의 코드로 파싱된 데이터를 가져올 수 있다.
try response.result.get()
} catch let error {
}
}
API를 통해 받아오는 response 값을 앱에서 사용할 수 있는 객체로 생성을 해주기 위해선 Mapping이 필요하다.
보통의 개발자들은 크게 2가지의 방법을 많이 쓰는 것으로 한번 정리를 해본다.
아래와 같이 사용할 수 있다.
import ObjectMapper
import AlamofireObjectMapper
import Alamofire
struct Post: Mappable {
var title: String
var user: String
var content: String
required init?(map: Map) {
}
func mapping(map: Map) {
title <- map["title"]
user <- map["user_name"]
content <- map["content"]
}
}
let url = ""
let params = ["page": page]
let header = ["Content-Type": "application/json"]
AF.request(url,
method: .get,
parameters: params,
encoder: URLEncodedFormParameterEncoder.default,
headers: header).responseObject { (response: DataResponse<[Post]> in
let response = response.result.value
}
Swift4에서부터 제공하는 기능으로 Swift에 내장되어 있는 Mapping 기능이라고 생각하면 된다.
역시 Alamofire와 함께 이용이 가능하고 사용법은 아래 링크를 참조하면 된다.
아무래도 별도의 라이브러리 없이 내장된 기능이다보니 최신버전의 Swift를 사용한다면 Codable을 이용하게 되는 것 같다.
실제 데이터를 가져오는 코드로 Alamofire 관련된 내용을 한번 봐 보자.
struct Post: Codable {
var id: Int
var title: String
var user: String
var content: String
}
static func getPosts(page: Int, success: @escaping([Post] -> Void)) {
let url = ""
let params = ["page": page]
let header = ["Content-Type": "application/json"]
AF.request(url,
method: .get,
parameters: params,
encoder: URLEncodedFormParameterEncoder.default,
headers: header)
.responseDecodable { (response: AFDataResponse<[Post]> in
guard let stateCode = response.response?.statusCode else {
return }
do {
} catch let error {
}
}
}
내용만 보면 크게 어려운 내용들은 없다.
그만큼 Alamofire가 꽤 직관적이고 사용하기 예쁜편이다.
그러나 API 구문들이 많아지면 호출 구문들이 상당히 많아지며, 복잡해지곤 한다. 의존성에 대한 문제도 있다.
만약 정말 훌륭한 개발자들이라면 자체적으로 APIManager나 APIService 같은 것들을 만들어서 잘 정리하시겠지.
그런데 이를 도와주는 라이브러리가 있다.
네트워크 추상화, 더 쉬운 테스트, 많은 API들을 Enum으로 정리할 수 있는 Moya 라이브러리다. (RxSwift도 따로 지원해서 궁합이 좋다고 한다.)
import Moya
//열거형으로 API들을 정의한다.
enum OderPlus {
//Parameter로 받는 것들과 함께 정의한다.
case order(Int)
}
//Moya는 TargetType을 따르면서 구현이 되는데 이부분이 큰 특징이다.
//enum으로 구현부를 정리를 해, 가독성 및 관리측면에서 많은 도움이 된다.
//아마 읽어보면 크게 더 설명 할 부분은 없어보인다.
extension OrderPlus: TargetType {
var baseURL: URL {
return URL(string: "http://api.orderplus.co.kr")!
}
var path: String {
switch self {
case .order:
return "order"
}
}
var method: Method {
switch self {
case .order:
return .post
}
}
var task: Task {
switch self {
case .order(let id):
return .requestParameters(
parameters: ["productId": id],
encoding: JSONEncoding.default
)
}
}
var headers: [String : String]? {
return nil
}
}
// 사용부
let provider = MoyaProvider<OrderPlus>()
provider.request(.order) { [weak self] result in
switch result {
case .success(let response):
case .failure:
}
}