Alamofire 공식 문서: https://github.com/Alamofire/Alamofire
Alamofire는 iOS, macOS를 위한 스위프트 기반 HTTP 네트워킹 라이브러리 이다.
애플에서 자체적으로 데이터/서버 통신을 위해 URLSession 이라는 것을 제공하지만, 여기서 불편한 점을 한번 더 보완한 것이 Alamofire라고 생각하면 된다.
Alamofire는 URLSession을 기반으로 하여 어려운 네트워킹 작업을 감춰주기 때문에 주요 로직에 집중할 수 있게 해준다. 그렇기 때문에 조금 더 쉽게 데이터에 접근할 수 있을 뿐만 아니라 코드의 가독성이 좋아진다.
정리를 하자면
Alamofire는 HTTP네트워킹을 하는데 자주 사용하게 되는 코드나 함수를 더 쉽게 사용할 수 있도록 모아놓은 것 이라고 생각하면 된다!
Request는 말 그대로 요청을 보내기 위해 사용하는 함수이다.
Alamofire에는 HTTPMethod라는 열거형이 저장되어있다.이 값들은 나중에 요청을 보낼때 method에 넣을 인자로 사용된다.공식문서에는 get 메소드에서 body에 값을 넣어서 보내는 것은 URLSession이나 Alamofire에서 지원하지 않는다고 나와있다.
Request가 있다면... Response가 있겠지..?
Response를 확인하고 싶다면 request 뒤에 response를 붙여주면 된다.
AF.request(URL).response()
그런데! Alamofire에는 6가지 Response Handler가 있다!
AF.request(url).response { response in
print(response)
}
AF.request(url).responseData { response in
print(response)
}
AF.request(url).responseString { response in
print(response)
}
AF.request(url).responseJSON { response in
print(response)
}
AF.request(url).responseDecodable { response in
print(response)
}
.validate()를 호출함으로써 요청의 대한 유효성 검사를 실시한다.(유효하지 않을경우 response가 넘어올 수 없다)
일단 일반적인 사용법은 이렇지만...
AF.request(url)
.validate()
.response { response in
print(response)
}
만약에 조건을 걸어주고 싶다면...
AF.request(url, method: .get, parameters: nil, headers: Constant.HEADER)
.validate(statusCode: 200..<500)
//200~500사이 상태만 허용
.validate(contentType: ["application/json"])
//JSON 포맷만 허용
.responseDecodable(of: PresentMoaHomeResponse.self) { response in
switch response.result {
case .success(let response):
viewController.serverData = response.data
viewController.didSuccessGot(message: response.message!)
case .failure(let error):
print(error.localizedDescription)
}
}
개인적으로 처음 앱잼 시작할때 이 validation 때문에 난관에 봉착했었다. 분명히 success로 빠져야 하는 것들이 자꾸 fail로 빠졌던 것... 그래서 저렇게 validate에 조건을 걸어줬더니 성공으로 잘 빠졌던 기억이 있다.
Alamofire Advanced Usage를 검색하던 도중에 Request Routing이라는 것을 발견했다.
Route: 경로, 길
집에 있는 라우터를 생각해보면, 하나의 네트워크 선이 라우터를 통해 여러 컴퓨터에 연결할 수도 있고, 와이파이도 만들어준다. 이처럼 앱에서도 수많은 API 요청이 발생하는데, Request Router를 통해 모든 요청을 생성할 수 있고, 이를 통해 하나의 파일에서 일관적으로 관리할 수 있다. 개인적으로 DataManager라는 파일을 뷰컨마다 한개씩 만들어서 쓰는 스타일이라서 여기저기 흩어져 있는 서버통신 관련 파일들을 하나로 볼 수 있다면 좋겠다는 생각이 들었다.
let baseURL = "https://httpbin.org"
class APIPath{
static let postPractice = "/post"
static let getPractice = "/get"
}
import Foundation
import Alamofire
class APIRouter: URLRequestConvertible {
// 1
enum APIType {
case auth
case service
var baseURL: String {
switch self {
case .auth:
switch enviroment {
case .dev: return "https://auth.dev"
case .stage: return "https://auth.dev"
case .real: return "https://auth.real"
}
case .service:
return "https://httpbin.org"
}
}
}
var path: String
var httpMethod: HTTPMethod
var parameters: Data?
var apiType: APIType
init(path: String, httpMethod: HTTPMethod? = .get, parameters: Data? = nil, apiType: APIType = .service) {
self.path = path
self.httpMethod = httpMethod ?? .get
self.parameters = parameters
self.apiType = apiType
}
func asURLRequest() throws -> URLRequest {
// 2. base URL + path
let fullURL = apiType.baseURL + path
let encodedURL = fullURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
var urlCompoenet = URLComponents(string: encodedURL)!
// 3. get> query parmeter 추가
if httpMethod == .get, let params = parameters {
if let dictionary = try? JSONSerialization.jsonObject(with: params, options: []) as? [String:Any] {
var queries = [URLQueryItem]()
for (name, value) in dictionary {
let encodedValue = "\(value)".addingPercentEncodingForRFC3986()
let queryItem = URLQueryItem(name: name, value: encodedValue)
queries.append(queryItem)
}
urlCompoenet.percentEncodedQueryItems = queries
}
}
// 4. request 생성
var request = try URLRequest(url: urlCompoenet.url!, method: httpMethod)
// 5. post> json parameter 추가
if httpMethod == .post, let params = parameters {
request.httpBody = params
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
}
// 6. header 추가
// if hasHeader {
// var headers = [String: String]()
// headers["Authorization"] = "toekn " + accessToken
// urlRequest.allHTTPHeaderFields = headers
// }
// 7. print to console
print("[REQUEST]")
print("URI: \(request.url?.absoluteString ?? "nil")")
print("Body: \(request.httpBody?.toPrettyPrintedString ?? NSString())")
return request
}
}
그럼 이제 실제로 어떻게 사용하냐!
import UIKit
import Alamofire
class ViewController: UIViewController {
var userInfo: UserInfo = {
var userInfo = UserInfo()
userInfo.userName = "yungso"
userInfo.age = 20
return userInfo
}()
override func viewDidLoad() {
super.viewDidLoad()
basicRequest()
getRequestByRouter()
postRequestByRouter()
requstUsingSession()
}
func basicRequest() {
let params: Parameters = ["userName": "yungso",
"age": 20]
AF.request("https://httpbin.org/post",
method: .post,
parameters: params).responseDecodable(of: HttpbinResponse.self, completionHandler: {(response: DataResponse<HttpbinResponse, AFError>) -> Void in
switch response.result {
case .success(let value):
print("[RESPONSE]")
print(response.data?.toPrettyPrintedString)
case .failure(let err):
print("API Failure")
print(err)
}
})
}
func postRequestByRouter() {
let router = APIRouter(path: APIPath.postPractice, httpMethod: .post, parameters: userInfo.toData, apiType: .service)
AF.request(router).responseDecodable(of: HttpbinResponse.self, completionHandler: {(response: DataResponse<HttpbinResponse, AFError>) -> Void in
switch response.result {
case .success(let value):
print("[RESPONSE]")
print("URL: \(value.url ?? "")")
print("json body: \(value.json ?? UserInfo())")
print("Response Data: \(response.data?.toPrettyPrintedString ?? "")")
case .failure(let err):
print("API Failure")
print(err)
}
})
}
Alamofire 공식문서
[Swift] Alamofire 사용해보기_(2)
Alamofire에 대해
[Swifit]Router로 API 요청하기-Alamafire Advanced Usage
Router 굉장히.. 좋은 방식인 것 같아 기술 공유 컨퍼런스 때 발표해주심 좋을 것 같다는 생각이 드네요..🌼👀ㅋ