var request = URLRequest(url: URL(String: urlString)!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
// code...
}
기존 URLSession에서 GET 방식의 request는 위와 같이 작성했었다.
만약 GET이 아니라 POST방식의 request를 작성하려면 아래와 같이 method도 바꿔야하고, parameter도 전달해주어야하고, 필요에 따라 header도 추가해주고 성공, 실패를 나누고... 너무 귀찮아진다.
var request = URLRequest(url: URL(string: urlString)!)
request.httpMethod = "POST"
let parameters = ["id": id, "password": password] as Dictionary
do {
try request.httpBody = JSONSerialization.data(withJSONObject: parameters, options: [])
} catch {
return
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let response = response as? HTTPURLResponse,
200..<300 ~= response.statusCode {
// success
} else {
// failure
}
}
이런식으로 단순히 method만 변경하려고 했는데 바꿔야할게 너무 많아서 불편하다. 이를 Alamofire를 사용해서 개선해보자.
Alamofire는 HTTP 통신할 때 사용하는 URLSession을 조금 더 쉽게 사용할 수 있도록 도와주는 라이브러리다.
Alamofire에서는
Alamofire에서 request는 2가지 방법으로 가능하다. cmd + 클릭을 해서 들어가보면 설명이 아주 잘 되어있다.
참고로 Alamofire ver5 이후부터 AF가 Session.default에 대한 참조이다.
DataRequest를 생성하면서 method, parameter 등의 configuration이 가능하다.
/// Creates a `DataRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and a
/// `RequestInterceptor`.
///
/// - Parameters:
/// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
/// - method: `HTTPMethod` for the `URLRequest`. `.get` by default.
/// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default.
/// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`.
/// `URLEncodedFormParameterEncoder.default` by default.
/// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
/// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
/// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from
/// the provided parameters. `nil` by default.
///
/// - Returns: The created `DataRequest`.
open func request<Parameters: Encodable>(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest {
let convertible = RequestEncodableConvertible(url: convertible,
method: method,
parameters: parameters,
encoder: encoder,
headers: headers,
requestModifier: requestModifier)
return request(convertible, interceptor: interceptor)
}
서버가 보안상의 이유로 인증된 사용자에게만 데이터를 줘야하는 상황에서, 어떻게 인증된 사용자인지 알 수 있을까?
HTTP header에 "Authorization"이란 key 값에 해당하는 value를 통해 인증된 사용자인지 알 수 있다. 이 access key는 앱에서 여러 API를 호출할 때마다 header로 보내야하므로 안전한 곳에 저장하여 어디에서나 접근할 수 있도록 해야한다.
따라서 Keychain-swift 라이브러리를 사용하는 것을 추천한다.
참고
request 메소드는 일반적인 매개변수들을 제공하지만 설정이 이것만으로 부족할 때 requestModifer를 사용하면 된다. requestModifier는 trailing closure로도 사용할 수 있다.
AF.request(urlString, requestModifier: { $0.timeoutInterval = 5 }).response(...)
AF.request(urlString) { urlRequest in
urlRequest.timeoutInterval = 5
urlRequest.allowsConstrainedNetworkAccess = false
}
.response(...)
Alamofire의 모든 URLRequestConvertible 프로토콜을 준수하는 모든 유형에 대해 DataRequest를 생성한다.
/// Creates a `DataRequest` from a `URLRequestConvertible` value and a `RequestInterceptor`.
///
/// - Parameters:
/// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
/// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
///
/// - Returns: The created `DataRequest`.
open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest {
let request = DataRequest(convertible: convertible,
underlyingQueue: rootQueue,
serializationQueue: serializationQueue,
eventMonitor: eventMonitor,
interceptor: interceptor,
delegate: self)
perform(request)
return request
}
request에 대한 response를 하기 전에 .validate()
를 호출해서 유효하지 않은 상태의 코드나 MIME 타입이 있는 경우 response를 하지 않도록 할 수 있다.
.validate()
는 자동으로 200..<300 범위 내 statusCode가 있고 Content-Type 헤더가 request의 Accept 헤더와 일치하는지 유효성을 검사한다.
// Automatic validation
AF.request(urlString).validate().responseJSON { response in
debugPrint(response)
}
// Manual validation
AF.request(url)
.validate(200..<300)
.validate(contentType: ["application/json"])
.response { response in
switch response.result {
case .success:
print("success")
case .failure(let error):
print(error
}
}
Alamofire에서는 앞서 살펴본 request 뒤에 response를 붙여주면 된다.
AF.request(url).response()
Alamofire는 기본적으로 6개의 response handler를 가지고 있다.
// Unserialzed Response
func response(queue: DispatchQueue = .main,
completionHandler: @escaping (AFDataResponse<Data?>) -> Void) -> Self
// Serialze using the passed Serialzer
func response<Serializer: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void)
AF.request(urlString).response { response in
debugPrint(response)
}
func responseData(queue: DispatchQueue = .main,
dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods,
completionHandler: @escaping (AFDataResponse<Data>) -> Void) -> Self
DataResponseSerialzer를 사용해서 서버에서 반환된 Data를 추출하고 유효성을 검사한다. 오류가 발생하지 않고 서버로부터 Data가 반환되면 Result는 .success이고 value는 서버에서 반환된 Data가 된다.
AF.request(urlString).responseData { response in
debugPrint(response)
}
func responseString(queue: DispatchQueue = .main,
dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
encoding: String.Encoding? = nil,
emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self
StringResponseSerializer를 사용해서 서버에서 반환된 Data를 지정된 인코딩을 사용하는 String으로 변환해준다. 오류가 발생하지 않고 서버로부터 Data를 받아와 String으로 변환되었다면 Result는 .success이고 value는 String 타입이 된다.
AF.request(urlString).responseString { response in
debugPrint(reponse)
}
func responseJSON(queue: DispatchQueue = .main,
dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (AFDataResponse<Any>) -> Void) -> Self
JSONResponseSerializer를 사용해서 서버에서 반환된 Data를 지정된 JSONSerializer.ReadingOptions를 사용해서 Any타입으로 변환해준다. 오류가 발생하지 않고 서버로부터 Data를 받아와 JSON object로 변환되었다면 Result는 .success이고 value는 Any타입이 된다.
AF.request(urlString).responseJSON { response in
debugPrint(response)
}
엥 테스트해보려고 했는데 Alamofire6부터 deprecated된단다... responseDecodable을 사용하도록 하자
func responseDecodable<T: Decodable>(of type: T.Type = T.self,
queue: DispatchQueue = .main,
dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
decoder: DataDecoder = JSONDecoder(),
emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self
DecodableResponseSerializer를 사용해서 서버에서 반환된 Data를 지정된 DataDecoder를 사용해서 전달된 Decodable타입으로 변환한다. 오류가 발생하지 않고 서버로부터 Data를 받아와 Decodable타입으로 디코딩되면 Result는 .success이고 value는 전달된 타입이 된다.
struct MyResponse: Decodable {
let url: String
}
AF.request(urlString).responseDecodable(of: MyResponse.self) { response in
debugPrint(response)
}
데이터를 메모리로 가져오는 것 말고 디스크에 파일로 다운로드를 하기 위해서 Alamofire는 다운로드 API도 제공한다.
AF.download(urlString).responseURL { response in
// read file from provided URL
}
responseURL은 다른 response handler들과 다르게 다운로드된 데이터의 위치가 포함된 URL만 반환하고 disk에서 data를 읽지 않는다.
responseDecodable과 같은 handler는 disk에서 data를 읽을 수 있다. 이 경우에는 큰 데이터를 메모리로 읽어올 수 있으므로 이러한 handler를 사용할 때에는 데이터 크기를 잘 고려해야 한다.
다운로드된 모든 데이터는 system temporary directory에 저장되어 결국 언젠가는 삭제가 된다. 따라서 오래 유지해야하는 경우에는 다른 곳으로 이동시켜야 한다. 이 때 Destination closure를 사용하면 된다.
destinationURL로 이동하기 전에 closure에 지정된 옵션이 실행된다.
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("image.png")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download(urlString, to: destination).response { response in
debugPrint(response)
if response.error == nil, let imagePath = response.fileURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
Alamofire Github
Alamofire란 무엇이고 어떻게 사용하는가?
iOS Swift 라이브러리 Alamofire 사용하기
[swift] Alamofire란? Alamofire 사용법 뽀개기
KeyChainSwift + Alamofire로 http header에 인증키 실어보내기
iOS) Alamofire(알라모파이어) 깃허브 문서를 요약해보자
Alamofire Tutorial (iOS 통신 라이브러리)