Alamofire를 사용하는 이유
URLSession 기반으로 만들어진 라이브러리로 코드를 더 간결화하고 가독성 있게 작성할 수 있습니다.
코드로 보는 차이점
URLSession
// URL Session Code
var request = URLRequest(url: URL(string: "https://api.github.com/users")!)
request.httpMethod = "POST"
let params = ["id":id, "password":password] as Dictionary
do {
try request.httpBody = JSONSerialization.data(withJSONObject: params, options: [])
} catch {
return
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
if let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode {
//성공 시 코드
}else{
//실패 시 코드
}
})
Alamofire
let params = ["id":id, "password":password] as Dictionary
AF.request("https://api.github.com/users", method: .get, parameters: param, encoding: URLEncoding.default, headers: ["Content-Type":"application/json", "Accept":"application/json"])
.validate(statusCode: 200..<300)
.responseJSON { (response) in
if let JSON = response.result.value
{
print(JSON)
}
}
Alamofire 특징
Alamofire는 HTTP 네트워크 요청에 대한 우아하고 조합 가능한 인터페이스를 제공합니다. 이는 자체 HTTP 네트워킹 기능을 구현하지 않고, 대신 Foundation 프레임워크에서 제공하는 Apple의 URL Loading System을 기반으로 구축됩니다. 시스템의 핵심은 URLSession과 URLSessionTask 하위 클래스입니다. Alamofire는 이러한 API와 여러 가지 다른 API를 더 쉽게 사용할 수 있는 인터페이스로 래핑하며, HTTP 네트워킹을 사용한 현대 애플리케이션 개발에 필요한 다양한 기능을 제공합니다. 그러나 Alamofire의 핵심 동작이 어디에서 오는지 알아야 하므로 URL Loading System에 대한 이해가 중요합니다. 결국, Alamofire의 네트워킹 기능은 해당 시스템의 기능에 의해 제한되며, 동작 및 최선의 사례를 항상 기억하고 준수해야 합니다.
또한, Alamofire에서 (및 일반적으로 URL Loading System에서) 네트워킹은 비동기적으로 수행됩니다. 비동기 프로그래밍은 이 개념에 익숙하지 않은 프로그래머들에게는 근원이 될 수 있는 분투의 원인이 될 수 있지만, 이 방법으로 수행하는 것에 대해 매우 좋은 이유가 있습니다.
추가 정보: AF 네임스페이스 및 참조
이전 버전의 Alamofire 문서에서는 Alamofire.request()와 같은 예제를 사용했습니다. 이 API는 Alamofire 접두사가 필요한 것처럼 보였지만, 사실은 그렇지 않았습니다. request 메서드와 기타 함수는 import Alamofire를 사용하여 전역적으로 사용할 수 있었습니다. 그러나 Alamofire 5에서는 이러한 기능이 제거되었으며, 대신 AF 글로벌은 Session.default에 대한 참조입니다. 이를 통해 Alamofire는 Alamofire가 사용될 때마다 전역 네임스페이스를 오염시키지 않고도 동일한 편리한 기능을 제공할 수 있으며, 전역적으로 Session API를 중복해서 사용할 필요가 없습니다. 마찬가지로, Alamofire에 의해 확장된 유형은 다른 확장에서 추가하는 기능과 구분하기 위해 af 속성 확장을 사용할 것입니다.
request 만들기
alamofire는 다양하고 편리한 HTTP request를 만들 수 있는 메서드를 제공합니다. 다음은 가장 간단한 방법으로 string 타입 url을 전달하여 사용하는 코드입니다.
AF.request("https://httpbin.org/get").response { response in
debugPrint(response)
}
모든 예제들은 import Alamofire 를 소스파일 내부에 명시해야 합니다
Swift의 async-await을 사용하려면 Advanced Usage 문서를 확인합니다
이것은 실제로 요청을 위한 Alamofire의 Session 타입의 두 가지 최상위 API의 한 형태이다. 완전한 정의는 다음과 같습니다.
open func request<Parameters: Encodable>(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil) -> DataRequest
이 메서드는 개별 구성 요소에서 요청을 구성하고, 메서드와 헤더와 같은 개별 구성 요소에서 요청을 구성하고, 개별 요청에 대한 RequestInterceptor와 Encodable 매개변수를 허용하는 동안 DataRequest를 생성합니다.
Parameters 딕셔너리과 ParameterEncoding 타입을 사용하여 요청할 수 있는 추가 메서드이 있습니다. 이 API는 더 이상 권장되지 않으며 결국 Alamofire에서 더 이상 사용되지 않고 제거될 것입니다.
이 API의 두 번째 버전은 훨씬 간단합니다:
open func request(_ urlRequest: URLRequestConvertible,
interceptor: RequestInterceptor? = nil) -> DataRequest
이 메서드는 Alamofire의 URLRequestConvertible 프로토콜을 준수하는 모든 타입에 대한 DataRequest를 생성합니다. 이전 버전의 모든 다양한 매개변수는 이 값에 캡슐화되어 있으며, 이는 매우 강력한 추상화를 만들 수 있습니다. 이에 대한 자세한 내용은 Advanced Usage 설명서에서 다룹니다.
HTTP Methods
HTTP Medthod에서 사용할 수 있는 종류들은 다음과 같습니다.
public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
public static let connect = HTTPMethod(rawValue: "CONNECT")
public static let delete = HTTPMethod(rawValue: "DELETE")
public static let get = HTTPMethod(rawValue: "GET")
public static let head = HTTPMethod(rawValue: "HEAD")
public static let options = HTTPMethod(rawValue: "OPTIONS")
public static let patch = HTTPMethod(rawValue: "PATCH")
public static let post = HTTPMethod(rawValue: "POST")
public static let put = HTTPMethod(rawValue: "PUT")
public static let query = HTTPMethod(rawValue: "QUERY")
public static let trace = HTTPMethod(rawValue: "TRACE")
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
위에 정의된 값들은 AF.request API에 method 인자에 다음과 같이 사용될 수 있습니다.
AF.request("https://httpbin.org/get")
AF.request("https://httpbin.org/post", method: .post)
AF.request("https://httpbin.org/put", method: .put)
AF.request("https://httpbin.org/delete", method: .delete)
다른 HTTP 메서드는 다른 의미를 가질 수 있으며 서버가 기대하는 것에 따라 다른 매개 변수 인코딩이 필요하다는 것을 기억하는 것이 중요합니다. 예를 들어, GET 요청에서 본문 데이터를 전달하는 것은 URLSession 또는 Alamofire에서 지원되지 않으며 오류를 반환합니다.
Alamofire는 또한 문자열을 HTTPMethod 값으로 반환하는 httpMethod 속성을 bridge하기 위해 URLRequest의 확장을 제공합니다.
extension URLRequest {
/// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.
public var method: HTTPMethod? {
get { httpMethod.flatMap(HTTPMethod.init) }
set { httpMethod = newValue?.rawValue }
}
}
만약 지원하지 않는 HTTPMethod 타입을 HTTP 메서드를 이용하여 사용하려하면 다음과 같이 extension을 통해 커스텀 값들을 추가할 수 있습니다.
extension HTTPMethod {
static let custom = HTTPMethod(rawValue: "CUSTOM")
}
AF.request("https://httpbin.org/headers", method: .custom)
다른 URLRequest 프로퍼티들 설정하기
Alamofire의 request 생성 메서드는 대부분의 사용자 정의를 위한 일반적인 매개변수를 제공하지만 때로는 그것만으로 충분하지 않을 수 있습니다. 전달된 값에서 생성된 URLRequests는 요청을 생성할 때 RequestModifier 클로저를 사용하여 수정할 수 있습니다. 예를 들어, URLRequest의 timeoutInterval을 5초로 설정하려면 클로저 내에서 요청을 수정하면 됩니다.
AF.request("https://httpbin.org/get", requestModifier: { $0.timeoutInterval = 5 }).response(...)
RequestModifier는 후행 클로저 구문과 함께 사용할 수도 있습니다.
AF.request("https://httpbin.org/get") { urlRequest in
urlRequest.timeoutInterval = 5
urlRequest.allowsConstrainedNetworkAccess = false
}
.response(...)
RequestModifier는 URL 및 다른 개별 구성 요소를 가져오는 메서드를 사용하여 생성된 요청에만 적용되며, URLRequestConvertible 값을 직접 만든 경우에는 해당 값이 모든 매개변수를 직접 설정할 수 있어야 하기 때문에 적용되지 않습니다. 또한 대부분의 요청이 생성 중에 수정되어야 할 때 URLRequestConvertible의 사용을 권장합니다. 자세한 내용은 Advanced Usage문서에서 확인할 수 있습니다.
Paramerter와 Parameter Encoder Request
Alamofire는 요청의 매개변수로 모든 Encodable 타입을 전달할 수 있습니다. 이러한 매개변수는 ParameterEncoder 프로토콜을 준수하는 타입을 통해 전달되고 URLRequest에 추가된 후 네트워크를 통해 전송됩니다. Alamofire에는 두 개의 ParameterEncoder 준수 타입이 포함되어 있습니다: JSONParameterEncoder와 URLEncodedFormParameterEncoder.
다음 코드 처럼 login 데이터를 JSONParameterEncoder를 사용하여 가져온 데이터를 encode하여 사용할 수 있게 됩니다.
struct Login: Encodable {
let email: String
let password: String
}
let login = Login(email: "test@test.test", password: "testPassword")
AF.request("https://httpbin.org/post",
method: .post,
parameters: login,
encoder: JSONParameterEncoder.default).response { response in
debugPrint(response)
}
URLEncodedFormParameterEncoder
URLEncodedFormParameterEncoder는 값을 URL 인코딩된 문자열로 인코딩하여 기존 URL 쿼리 문자열에 설정하거나 추가하거나 요청의 HTTP 본문으로 설정합니다. 인코딩된 문자열이 어디에 설정되는지를 제어하는 것은 인코딩의 대상을 설정하는 것으로 수행할 수 있습니다. URLEncodedFormParameterEncoder.Destination 열거형에는 세 가지 케이스가 있습니다:
- .methodDependent - .get, .head 및 .delete 요청에 대해 기존 쿼리 문자열에 인코딩된 쿼리 문자열 결과를 적용하고 다른 HTTP 메서드를 사용하는 요청에는 이를 HTTP 본문으로 설정합니다.
- .queryString - 인코딩된 문자열을 요청 URL의 쿼리로 설정하거나 추가합니다.
- .httpBody - 인코딩된 문자열을 URLRequest의 HTTP 본문으로 설정합니다.
HTTP 본문을 가진 인코딩된 요청의 Content-Type HTTP 헤더는 Content-Type이 이미 설정되지 않았다면 application/x-www-form-urlencoded; charset=utf-8로 설정됩니다.
내부적으로 URLEncodedFormParameterEncoder는 URLEncodedFormEncoder를 사용하여 Encodable 타입을 URL 인코딩된 형태의 문자열로 실제로 인코딩합니다.
ArrayEncoding을 사용하여 Array을 인코딩할 때 사용합니다. 배열의 요소를 인코딩하여 URL 인코딩된 문자열에 추가합니다.
BoolEncoding을 사용하여 Bool을 인코딩할 때 사용합니다. Bool 값을 인코딩하여 URL 인코딩된 문자열에 추가합니다.
DataEncoding을 사용하여 Data을 인코딩할 때 사용합니다. Data 값을 인코딩하여 URL 인코딩된 문자열에 추가합니다.
DateEncoding을 사용하여 Date을 인코딩할 때 사용합니다. Date 값을 인코딩하여 URL 인코딩된 문자열에 추가합니다.
KeyEncoding을 사용하여 코딩 키를 인코딩할 때 사용합니다. 코딩 키를 인코딩하여 URL 인코딩된 문자열에 추가합니다.
SpaceEncoding을 사용하여 공백을 포함한 여러 유형을 인코딩할 때 사용합니다. URL 인코딩된 문자열에 추가합니다.
위와 같은 옵션들로 사용자 정의하는 데 사용할 수 있습니다.
URL-Encoded Parameter를 사용한 GET Request
URLEncodedFormParameterEncoder: parameters 값을 URL 인코딩된 형식으로 변환하여 URL에 parameters 값을 반영합니다.
let parameters = ["foo": "bar"]
// All three of these calls are equivalent
AF.request("https://httpbin.org/get", parameters: parameters) // encoding defaults to `URLEncoding.default`
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))
// https://httpbin.org/get?foo=bar
Alamofire를 이용하여 parameter 넣어서 서버에 JSON 값 요청하기
func getTest(url: URL, bpm: Int) {
do {
let parameters = ["heartRate": bpm.description]
let token = try KeyChain.get()
let url = url // ?heartRate={bpm}
let header: HTTPHeaders = [.authorization(bearerToken: token.token)]
print(url)
AF.request(url,
method: .get,
parameters: parameters as Parameters,
encoding: URLEncoding.default,
headers: header)
.validate(statusCode: 200..<300)
.responseDecodable(of: MusicInfoModel.self) { response in
Task {
await self.setupMusicInfo(url: URL(string: response.value?.filePath ?? "")!, info: response.value!)
}
}
} catch {
print("token error:\(error)")
}
}
URL-Encoded Parameter를 이용한 POST Request
URLEncodedFormParameterEncoder: parameters 값을 URL 인코딩된 형식으로 변환하여 URL에 parameters 값을 반영합니다.
let parameters: [String: [String]] = [
"foo": ["bar"],
"baz": ["a", "b"],
"qux": ["x", "y", "z"]
]
// All three of these calls are equivalent
AF.request("https://httpbin.org/post", method: .post, parameters: parameters)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .httpBody))
// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"
인코딩된 값의 정렬 구성하기
Swift 4.2부터 Swift의 Dictionary 타입이 사용하는 해싱 알고리즘은 런타임에서 무작위의 내부 순서를 생성하며 이는 앱 실행 시마다 다를 수 있습니다. 이는 인코딩된 매개변수의 순서를 변경할 수 있으며 이는 캐싱 및 기타 동작에 영향을 줄 수 있습니다. 기본적으로URLEncodedFormEncoder는 인코딩된 키-값 쌍을 정렬합니다. 이렇게 하면 모든 Encodable 타입에 대해 일관된 출력이 생성되지만 실제로 구현된 인코딩 순서와 일치하지 않을 수 있습니다. alphabetizeKeyValuePairs를 false로 설정하여 구현 순서로 돌아갈 수 있지만, 이 경우에도 Dictionary의 무작위 순서가 발생합니다.
원하는 alphabetizeKeyValuePairs를 지정하여 사용자 지정 URLEncodedFormParameterEncoder를 생성할 수 있으며 이는 전달된 URLEncodedFormEncoder의 이니셜라이저에서 설정할 수 있습니다.
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(alphabetizeKeyValuePairs: false))
인코딩된 배열 Parameter 구성하기
collection 타입을 인코딩하는 방법에 대한 알려진 명세가 없기 때문에, Alamofire는 기본적으로 배열 값에 대한 키 뒤에 []를 추가하는 규칙을 따릅니다 (예: foo[]=1&foo[]=2), 그리고 중첩된 딕셔너리 값에 대해서는 키를 대괄호로 둘러싸고 그 뒤에 추가합니다 (예: foo[bar]=baz).
URLEncodedFormEncoder.ArrayEncoding 열거형은 배열 매개변수를 인코딩하는 다음과 같은 메서드를 제공합니다:
* .brackets: 각 값에 대해 빈 대괄호가 키 뒤에 추가됩니다. 이것이 기본적인 경우입니다.
* .noBrackets: 대괄호가 추가되지 않습니다. 키는 그대로 인코딩됩니다.
기본적으로 Alamofire는 .brackets 인코딩을 사용합니다. 예를 들어, foo = [1, 2]는 foo[]=1&foo[]=2로 인코딩됩니다.
.noBrackets 인코딩을 사용하면 foo = [1, 2]가 foo=1&foo=2로 인코딩됩니다.
원하는 방식으로 동작을 구성하려면 자체 URLEncodedFormParameterEncoder를 생성하고 전달된 URLEncodedFormEncoder의 이니셜라이저에서 원하는 ArrayEncoding을 지정할 수 있습니다.
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(arrayEncoding: .noBrackets))
인코딩된 Bool Parameter 구성하기
URLEncodedFormEncoder.BoolEncoding enumeration은 다음과 같은 메서드를 제공하여 Bool Parameters를 인코딩합니다.
* .numeric - true를 1, false를 0으로 인코딩하며 기본 값입니다.
* .literal - true와 false를 string 문자 자체로 인코딩합니다.
자신만의 URLEncodedFormParameterEncoder를 만들고 전달된 URLEncodedFormEncoder의 이니셜라이저에서 원하는 BoolEncoding을 지정할 수 있습니다.
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(boolEncoding: .numeric))
인코딩된 Data Parameter 구성하기
DataEncoding은 다음과 같은 메서드를 제공하여 Data Parameters를 인코딩합니다.
* .deferredToData - 데이터의 Encodable를 지원합니다.
* .base64 - 데이터를 Base 64로 인코딩된 문자열로 인코딩합니다.
* .custom((Data) -> throws -> String) - 주어진 클로저를 사용하여 데이터를 인코딩합니다.
자신만의 URLEncodedFormParameterEncoder를 만들고 전달된 URLEncodedForEncoder의 이니셜라이저에서 원하는 DataEncoding을 지정할 수 있습니다.
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dataEncoding: .base64))
인코딩된 Date Parameter 구성하기
DateEocoding은 다음과 같은 메서드를 제공하여 Date Parameters를 인코딩합니다.
* .deferredToDate: Date의 기본 Encodable 지원을 사용합니다. 이것이 기본적인 경우입니다.
* .secondsSince1970: Dates를 1970년 1월 1일 자정부터의 UTC 시간에서의 초 단위로 인코딩합니다.
* .millisecondsSince1970: Dates를 1970년 1월 1일 자정부터의 UTC 시간에서의 밀리초 단위로 인코딩합니다.
* .iso8601: Dates를 ISO 8601 및 RFC3339 표준에 따라 인코딩합니다.
* .formatted(DateFormatter): 주어진 DateFormatter를 사용하여 Dates를 인코딩합니다.
* .custom((Date) throws -> String): 주어진 클로저를 사용하여 Dates를 인코딩합니다.
자신만의 URLEncodedFormParameterEncoder를 생성하고 전달된 URLEncodedFormEncoder의 이니셜라이저에서 원하는 DateEncoding을 지정할 수 있습니다.
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dateEncoding: .iso8601))
인코딩된 Coding Keys 구성하기
다양한 parameter key 스타일로 인해 KeyEncoding은 lowerCamelCase의 키에서 키 인코딩을 사용자 정의하는 다음과 같은 방법을 제공합니다.
* .useDefaultKeys: 각 타입에서 지정된 키를 사용합니다. 이것이 기본적인 경우입니다.
* .convertToSnakeCase: 키를 snake 케이스로 변환합니다: oneTwoThree는 one_two_three가 됩니다.
* .convertToKebabCase: 키를 kebab 케이스로 변환합니다: oneTwoThree는 one-two-three가 됩니다.
* .capitalized: 첫 글자만 대문자로 변환합니다, 즉 UpperCamelCase: oneTwoThree는 OneTwoThree가 됩니다.
* .uppercased: 모든 글자를 대문자로 변환합니다: oneTwoThree는 ONETWOTHREE가 됩니다.
* .lowercased: 모든 글자를 소문자로 변환합니다: oneTwoThree는 onetwothree가 됩니다.
* .custom((String) -> String): 주어진 클로저를 사용하여 키를 인코딩합니다.
자신만의 URLEncodedFormParameterEncoder를 생성하고 전달된 URLEncodedFormEncoder의 이니셜라이저에서 원하는 KeyEncoding을 지정할 수 있습니다:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(keyEncoding: .convertToSnakeCase))
JSONParameterEncoder
JSONParameterEncoder는 Swift의 JSONEncoder를 사용하여 Encodable 값을 인코드하고 결과를 URLRequest의 httpBody로 정합니다. 인코딩된 요청의 Content-Type HTTP 헤더 필드는 아직 설정되지 않은 경우 application/json으로 설정됩니다.
JSON-Encoded Parameter로 POST 요청하기
let parameters: [String: [String]] = [
"foo": ["bar"],
"baz": ["a", "b"],
"qux": ["x", "y", "z"]
]
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.prettyPrinted)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.sortedKeys)
// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}
func getTest() {
let test = ["Hello":"World"]
AF.request("https://httpbin.org/get", parameters: test, encoder: URLEncodedFormParameterEncoder.default).response { result in
print(result.response?.url ?? "No URL")
}
}
커스텀 JSONEncoder 구성하기
자신만의 JSONParameterEncoder를 생성하고 JSONEncoder 인스턴스를 전달하여 구성할 수 있습니다.
let encoder = JSONEncoder()
encoder.dateEncoding = .iso8601
encoder.keyEncodingStrategy = .convertToSnakeCase
let parameterEncoder = JSONParameterEncoder(encoder: encoder)
URLRequest의 수동 매개변수 인코딩
ParameterEncoder API는 Alamofire 외부에서도 사용할 수 있으며, 매개변수를 직접 URLRequest에 인코딩하여 사용할 수 있습니다.
let url = URL(string: "https://httpbin.org/get")!
var urlRequest = URLRequest(url: url)
let parameters = ["foo": "bar"]
let encodedURLRequest = try URLEncodedFormParameterEncoder.default.encode(parameters,
into: urlRequest)
HTTP Headers
Alamofire는 HTTPHeaders 타입을 포함시킬 수 있습니다. 다음과 같이 키-값 으로 구성할 수 있습니다.
let headers: HTTPHeaders = [
"Authorization": "Basic VXNlcm5hbWU6UGFzc3dvcmQ=",
"Accept": "application/json"
]
AF.request("https://httpbin.org/headers", headers: headers).responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
HTTPHeaders는 또한 HTTPHeader 값의 배열에서 구성될 수 있습니다.
let headers: HTTPHeaders = [
.authorization(username: "Username", password: "Password"),
.accept("application/json")
]
AF.request("https://httpbin.org/headers", headers: headers).responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
Response 유효성 검사
Alamofire는 기본적으로 응답의 내용에 관계없이 완료된 요청을 성공으로 처리합니다. 응답 핸들러 이전에 validate()를 호출하면 응답이 부적절한 상태 코드나 MIME 타이을 가진 경우에 오류가 발생합니다.
자동 검증
validate() API는 자동으로 상태 코드가 200..<300 범위 내에 있는지 및 요청에 Accept 헤더가 제공된 경우 응답의 Content-Type 헤더가 Accept 헤더와 일치하는지를 검증합니다.
AF.request("https://httpbin.org/get").validate().responseData { response in
debugPrint(response)
}
메뉴얼 검증
AF.request("https://httpbin.org/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseData { response in
switch response.result {
case .success:
print("Validation Successful")
case let .failure(error):
print(error)
}
}
Response Handling
Response로 받은 Data Handler
Alamofire의 DataRequest 및 DownloadRequest에는 각각 DataResponse<Success, Failure: Error> 및DownloadResponse<Success, Failure: Error>라는 해당하는 응답 유형이 있습니다. 이 두 유형은 두 개의 제네릭을 구성하고 있습니다: 직렬화된 유형 및 오류 유형입니다. 기본적으로 모든 응답 값은 AFError 오류 유형(즉, DataResponse<Success, AFError>)을 생성합니다. Alamofire는 항상 AFError 오류 유형을 사용하는 단순한 AFDataResponse<Success> 및AFDownloadResponse<Success>를 사용하여 공개 API에서 이를 간소화합니다. UploadRequest인 DataRequest의 하위 클래스도 동일한 DataResponse 유형을 사용합니다.
Alamofire에서 DataRequest 또는 UploadRequest로 수행한 DataResponse를 처리하는 방법은 responseDecodable과 같은 응답 핸들러를 DataRequest에 체인으로 연결하여 지정한 DecodableType 타입으로 디코딩하여 response를 가져옵니다.
AF.request("https://httpbin.org/get").responseDecodable(of: MusicInfoModel.self) { response in
debugPrint(response)
}
위의 예에서 responseDecodable 핸들러가 DataRequest(.reqeust)에 추가되어 DataRequest가 완료되면 실행되도록 설정됩니다. 핸들러에 전달된 클로저는 요청에서 생성된 URLRequest, HTTPURLResponse, Data 및 Error로부터 생성된 DataResponse<DecodableType, AFError> 값을 받습니다.
서버로부터 응답을 기다리는 데 실행을 차단하는 대신, 이 클로저는 응답을 받은 후에 처리할 콜백으로 추가됩니다. 요청의 결과는 응답 클로저의 범위 내에서만 사용할 수 있습니다. 서버로부터 받은 응답 또는 데이터에 의존하는 실행은 응답 클로저 내에서 수행되어야 합니다.
Alamofire로 이루어지는 네트워킹은 비동기적입니다. 더 알아보기
Alamofire는 기본적으로 다음과 같은 5개의 다른 data response handler를 포함하고 있습니다.
// Response Handler - Unserialized Response
func response(queue: DispatchQueue = .main,
completionHandler: @escaping (AFDataResponse<Data?>) -> Void) -> Self
// Response Serializer Handler - Serialize using the passed Serializer
func response<Serializer: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void) -> Self
// Response Data Handler - Serialized into Data
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
// Response String Handler - Serialized into String
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
// Response Decodable Handler - Serialized into Decodable Type
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
response handler 중 어느 것도 서버에서 돌아오는 HTTPURLResponse의 검증을 수행하지 않습니다.
예를 들어, response의 status code가 400..<500 과 500..<600 에 있어도 에러를 발생시키지 않습니다. Alamofire는 이를 위해 Response Validation 를 사용합니다.
Response Handler
response handler는 response를 평가하지 않습니다. 단순히 URLSessionDelegate에서 직접 모든 정보를 전달합니다. 이것은 요청을 실행하기 위해 cURL을 사용하는 것과 유사한 Alamofire의 동작입니다.
AF.request("https://httpbin.org/get").response { response in
debugPrint("Response: \(response)")
}
Response로 받은 Data Handler
responseData handler는 DataResponseSerializer를 사용하여 서버로부터 받은 Data를 명시된 인코딩을 통해 Data으로 반환합니다. 만약 성공적으로 데이터를 직렬화하면 response의 Result는 .success로 value의 타입은 Data이 됩니다
AF.request("https://httpbin.org/get").responseData { response in
debugPrint("Response: \(response)")
}
Response로 받은 String Handler
responseString handler는 StringResponseSerializer를 사용하여 서버로부터 받은 Data를 명시된 인코딩을 통해 String으로 반환합니다. 만약 성공적으로 데이터를 직렬화하면 response의 Result는 .success로 value의 타입은 String이 됩니다
AF.request("https://httpbin.org/get").responseString { response in
debugPrint("Response: \(response)")
}
Response Decodable Handler
responseDecodable handler는 DecodableResponseSerializer를 사용하여 서버에서 반환된 데이터를 지정된 Decodable 유형으로 변환합니다. 이때 지정된 DataDecoder를 사용하여 변환을 수행합니다. 변환 과정에서 오류가 발생하지 않고 서버 데이터가 성공적으로 Decodable 유형으로 디코딩되면 응답 결과는 .success가 되고 해당 값은 전달된 유형이 됩니다.
struct DecodableType: Decodable { let url: String }
AF.request("https://httpbin.org/get").responseDecodable(of: DecodableType.self) { response in
debugPrint("Response: \(response)")
}
Chained Response Handlers
response handler들끼리 조합하여 사용할 수 있습니다.
Alamofire.request("https://httpbin.org/get")
.responseString { response in
print("Response String: \(response.value)")
}
.responseDecodable(of: DecodableType.self) { response in
print("Response DecodableType: \(response.value)")
}
Response Handler Queue
response handler에 전달된 클로저는 기본적으로 .main queue에서 실행되지만, 특정 DispatchQueue를 전달하여 클로저를 실행할 수 있습니다. 실제 직렬화 작업(데이터를 다른 유형으로 변환)은 항상 요청을 발행하는 session의 rootQueue 또는 serializationQueue(지정된 경우)에서 백그라운드에서 실행됩니다.
let utilityQueue = DispatchQueue.global(qos: .utility)
AF.request("https://httpbin.org/get").responseDecodable(of: DecodableType.self, queue: utilityQueue) { response in
print("This closure is executed on utilityQueue.")
debugPrint(response)
}
Response Caching
응답 캐싱은 URLCache 시스템 프레임워크 수준에서 처리됩니다. 이는 메모리와 디스크에 모두 캐시를 제공하며 메모리와 디스크 부분의 크기를 조절할 수 있도록 합니다.
기본적으로 Alamofire는 URLCache.shared 인스턴스를 활용합니다. 사용하는 URLCache 인스턴스를 사용자 정의하려면 여기를 참조하십시오.
Authentication
Authentication은 URLCredential과 URLAuthenticationChallenge 시스템 프레임워크 수준에서 처리됩니다.
이러한 인증 API는 권한 부여를 요청하는 서버를 위한 것이며, 일반적으로 Authenticate 또는 동등한 헤더를 필요로 하는 API와는 일반적으로 사용되지 않습니다.
HTTP 기존 Authentication
Request의 authenticate 메서드는 적절한 경우 URLAuthenticationChallenge에 challenged했을 때 자동으로 URLCredential을 제공합니다.
let user = "user"
let password = "password"
AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(username: user, password: password)
.responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
URLCredential를 사용한 인증
let user = "user"
let password = "password"
let credential = URLCredential(user: user, password: password, persistence: .forSession)
AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(with: credential)
.responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
URLCredential을 사용하여 인증할 때 주의할 점은 실제로 서버에서 도전이 발생하면 기본 URLSession이 두 번의 요청을 수행한다는 것입니다. 첫 번째 요청은 자격 증명을 포함하지 않을 수 있으며 이로 인해 서버로부터 도전을 유발할 수 있습니다. 그런 다음 Alamofire에서 도전을 수신하고 자격 증명이 추가되어 기본 URLSession에서 요청이 다시 시도됩니다.
메뉴얼 인증
만약 항상 인증(Authenticate) 또는 유사한 헤더가 필요한 API와 통신하는 경우, 이를 수동으로 추가할 수 있습니다.
let user = "user"
let password = "password"
let headers: HTTPHeaders = [.authorization(username: user, password: password)]
AF.request("https://httpbin.org/basic-auth/user/password", headers: headers)
.responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
그러나 모든 요청에 포함되어야 하는 헤더는 종종 사용자 지정 URLSessionConfiguration의 일부로 더 잘 처리되거나 RequestAdapter를 사용하여 처리하는 것이 좋습니다.
파일에 데이터 다운 받기
추가적으로 데이터를 메모리에 가져오는 것 외에도 Alamofire는 Session.download, DownloadRequest, DownloadResponse<Success, Failure: Error> API를 제공하여 디스크에 쉽게 저장할 수 있습니다. 메모리로 다운로드하는 것은 대부분의 JSON API 응답과 같은 작은 페이로드에 적합하지만, 이미지와 비디오와 같은 더 큰 자산을 가져오는 것은 애플리케이션의 메모리 문제를 피하기 위해 디스크에 다운로드해야 합니다.
AF.download("https://httpbin.org/image/png").responseURL { response in
// Read file from provided file URL.
}
DataRequest와 마찬가지로 DownloadRequest도 동일한 응답 핸들러를 가지고 있지만, responseURL도 포함되어 있습니다. 다른 Response Handler와는 달리, 이 Handler는 다운로드된 데이터의 위치를 포함하는 URL을 반환하며 디스크에서 데이터를 읽지 않습니다.
responseDecodable과 같은 다른 Response Handler는 디스크에서 response Data를 읽어들입니다. 이는 많은 양의 데이터를 메모리로 읽어들일 수 있기 때문에 이러한 핸들러를 다운로드에 사용할 때 이를 염두에 두는 것이 중요합니다.
지정한 경로에 파일 다운 받기
모든 다운로드된 데이터는 초기에 시스템 임시 디렉토리에 저장됩니다. 이 데이터는 앞으로 시스템에 의해 언젠가 삭제될 것이므로 더 오래 지속되어야 하는 경우 파일을 다른 곳으로 이동하는 것이 중요합니다.
임시 파일이 실제로 대상 URL로 이동되기 전에 클로저에 지정된 옵션들이 실행됩니다. 현재 지원되는 두 가지 옵션은 다음과 같습니다:
.createIntermediateDirectories - 대상 URL의 중간 디렉토리를 생성합니다(지정된 경우).
.removePreviousFile - 대상 URL에서 이전 파일을 제거합니다(지정된 경우).
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("https://httpbin.org/image/png", to: destination).response { response in
debugPrint(response)
if response.error == nil, let imagePath = response.fileURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
추천된 다운로드 대상 API도 사용할 수 있습니다.
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
AF.download("https://httpbin.org/image/png", to: destination)
다운로드 진행 상황
사용자한테 현재 다운로드 진행 사항을 알려주는 것은 유용할 때가 많습니다. DownloadRequest는 downloadProgress API를 사용하여 다운로드 진행 사항을 알려줄 수 있습니다.
AF.download("https://httpbin.org/image/png")
.downloadProgress { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
URLSession 및 따라서 Alamofire의 진행률 보고 API는 서버가 적절한 Content-Length 헤더를 제대로 반환하는 경우에만 작동합니다. 이 헤더를 사용하여 진행률을 계산할 수 있습니다. 이 헤더가 없는 경우 진행률은 다운로드가 완료될 때까지 0.0으로 유지되며, 그 시점에서 진행률은 1.0으로 즉시 이동합니다.
다운로드 취소와 재개하기
모든 Request 클래스에 있는 cancel() API 외에도 DownloadRequest는 나중에 다운로드를 재개하는 데 사용할 수 있는 재개 데이터(resume data)를 생성할 수 있습니다. 이 API에는 두 가지 형태가 있습니다: cancel(producingResumeData: Bool)는 재개 데이터를 생성할지 여부를 제어할 수 있지만, 재개 데이터는 DownloadResponse에서만 사용할 수 있습니다. 반면, cancel(byProducingResumeData: (_ resumeData: Data?) -> Void)는 동일한 작업을 수행하지만 재개 데이터를 완료 핸들러(completion handler)에서 사용할 수 있습니다.
만약 DownloadRequest가 취소되거나 중단된 경우, 기본 URLSessionDownloadTask가 재개 데이터를 생성할 수 있습니다. 이 경우 재개 데이터를 사용하여 DownloadRequest를 중단된 지점부터 다시 시작할 수 있습니다.
중요: 모든 Apple 플랫폼의 일부 버전(iOS 10 - 10.2, macOS 10.12 - 10.12.2, tvOS 10 - 10.1, watchOS 3 - 3.1.1)에서는 백그라운드 URLSessionConfigurations에서 재개 데이터가 작동하지 않습니다. 재개 데이터 생성 로직에 있는 기본 버그로 인해 데이터가 잘못 작성되어 항상 다운로드 재개에 실패합니다. 이 버그와 가능한 해결 방법에 대한 자세한 정보는 이 Stack Overflow 게시물을 참조하세요.
var resumeData: Data!
let download = AF.download("https://httpbin.org/image/png").responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
// download.cancel(producingResumeData: true) // Makes resumeData available in response only.
download.cancel { data in
resumeData = data
}
AF.download(resumingWith: resumeData).responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
서버에 데이터 업로드하기
서버로 JSON 또는 URL 인코딩된 매개변수를 사용하여 상대적으로 적은 양의 데이터를 전송할 때는 request() API가 일반적으로 충분합니다. 그러나 메모리에 있는 데이터, 파일 URL 또는 InputStream에서 훨씬 큰 양의 데이터를 전송해야 하는 경우 upload() API를 사용해야 합니다.
데이터 업로드하기
let data = Data("data".utf8)
AF.upload(data, to: "https://httpbin.org/post").responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
파일 업로드하기
let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
AF.upload(fileURL, to: "https://httpbin.org/post").responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
여러 유형으로 된 데이터 업로드하기
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(Data("one".utf8), withName: "one")
multipartFormData.append(Data("two".utf8), withName: "two")
}, to: "https://httpbin.org/post")
.responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
진행사항 업로드하기
사용자가 업로드가 완료될 때까지 기다리는 동안 업로드의 진행 상황을 사용자에게 표시하는 것이 유용할 수 있습니다. 모든 UploadRequest는 uploadProgress 및 downloadProgress API를 사용하여 업로드의 진행 상황 및 응답 데이터 다운로드의 다운로드 진행 상황을 모두 보고할 수 있습니다.
let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
AF.upload(fileURL, to: "https://httpbin.org/post")
.uploadProgress { progress in
print("Upload Progress: \(progress.fractionCompleted)")
}
.downloadProgress { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseDecodable(of: DecodableType.self) { response in
debugPrint(response)
}
서버로부터 데이터 스트리밍하기
대량 다운로드 또는 오랜 시간 동안 지속되는 서버 연결은 시간이 지남에 따라 데이터를 받는 스트리밍을 사용하는 것이 더 나을 수 있습니다. Alamofire는 이 사용 사례를 처리하기 위한 DataStreamRequest 타입 및 관련 API를 제공합니다. 다른 요청과 유사한 API를 많이 제공하지만, 몇 가지 주요한 차이점이 있습니다. 특히 DataStreamRequest는 메모리에 데이터를 누적하거나 디스크에 저장하지 않습니다. 대신에 추가된 responseStream 클로저는 데이터가 도착할 때마다 반복적으로 호출됩니다. 동일한 클로저는 연결이 완료되었거나 오류를 수신했을 때 다시 호출됩니다.
각 Handler 클로저는 처리 중인 이벤트와 취소할 수 있는 CancellationToken 값 챕쳐합니다. 을 캡처합니다.
public struct Stream<Success, Failure: Error> {
/// Latest `Event` from the stream.
public let event: Event<Success, Failure>
/// Token used to cancel the stream.
public let token: CancellationToken
/// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`.
public func cancel() {
token.cancel()
}
}
Event은 두 가지 가능한 스트림 상태를 나타내는 열거형입니다.
public enum Event<Success, Failure: Error> {
/// Output produced every time the instance receives additional `Data`. The associated value contains the
/// `Result` of processing the incoming `Data`.
case stream(Result<Success, Failure>)
/// Output produced when the instance has completed, whether due to stream end, cancellation, or an error.
/// Associated `Completion` value contains the final state.
case complete(Completion)
}
스트리밍 데이터
서버로부터 받은 데이터는 다른 Alamofire request들처럼 완료될 수 있지만 Handler 클로저가 추가됩니다.
func responseStream(on queue: DispatchQueue = .main, stream: @escaping Handler<Data, Never>) -> Self
제공된 queue는 핸들러 클로저가 호출될 곳입니다.
AF.streamRequest(...).responseStream { stream in
switch stream.event {
case let .stream(result):
switch result {
case let .success(data):
print(data)
}
case let .complete(completion):
print(completion)
}
}
위의 예에서 Result의 .failure 케이스를 처리하는 것은 불필요합니다. 왜냐하면 데이터를 받는 것은 결코 실패하지 않기 때문입니다.
String 스트리밍
Data 스트리밍처럼 String 또한 Handler를 추가하여 스트림될 수 있습니다.
func responseStreamString(on queue: DispatchQueue = .main,
stream: @escaping StreamHandler<String, Never>) -> Self
String은 UTF8로 디코딩되어 실패하지 않습니다.
AF.streamRequest(...).responseStreamString { stream in
switch stream.event {
case let .stream(result):
switch result {
case let .success(string):
print(string)
}
case let .complete(completion):
print(completion)
}
}
Decodable 값 스트리밍
들어오는 스트림 데이터 값은 responseStreamDecodable을 사용하여 어떤 Decodable 값으로든 변환할 수 있습니다.
func responseStreamDecodable<T: Decodable>(of type: T.Type = T.self,
on queue: DispatchQueue = .main,
using decoder: DataDecoder = JSONDecoder(),
preprocessor: DataPreprocessor = PassthroughPreprocessor(),
stream: @escaping Handler<T, AFError>) -> Self
디코딩 실패는 스트림을 종료하지 않고 대신 결과의 출력(Result of the Output)에 AFError를 생성합니다.
AF.streamRequest(...).responseStreamDecodable(of: SomeType.self) { stream in
switch stream.event {
case let .stream(result):
switch result {
case let .success(value):
print(value)
case let .failure(error):
print(error)
}
case let .complete(completion):
print(completion)
}
}
스트림 핸들러(StreamHandler) 클로저를 사용하여 들어오는 데이터를 처리하는 것 외에도, DataStreamRequest는 도착하는 바이트를 읽을 수 있는 InputStream 값을 생성할 수 있습니다.
func asInputStream(bufferSize: Int = 1024) -> InputStream
이 방식으로 생성된 InputStream은 읽기를 시작하기 전에 open()을 호출하거나 자동으로 스트림을 열어주는 API에 전달되어야 합니다. 이 메서드에서 반환된 후에는 InputStream 값을 호출자가 유지하고 읽기가 완료된 후에 close()를 호출하는 것이 호출자의 책임입니다.
let inputStream = AF.streamRequest(...)
.responseStream { output in
...
}
.asInputStream()
취소
DataStreamRequest는 네 가지 방법으로 취소할 수 있습니다. 첫째, 다른 모든 Alamofire 요청과 마찬가지로 DataStreamRequest에 cancel()을 호출하여 기본 작업을 취소하고 스트림을 완료할 수 있습니다.
let request = AF.streamRequest(...).responseStream(...)
...
request.cancel()
둘째, DataStreamRequests는 DataStreamSerializer가 오류를 만났을 때 자동으로 취소될 수 있습니다. 이 동작은 기본적으로 비활성화되어 있으며 요청을 생성할 때 automaticallyCancelOnStreamError 매개변수를 전달하여 활성화할 수 있습니다.
AF.streamRequest(..., automaticallyCancelOnStreamError: true).responseStream(...)
셋째, Handler 클로저에서 오류가 throw되면 DataStreamRequest가 취소됩니다. 이 오류는 요청에 저장되어 Completion 값에서 사용할 수 있습니다.
AF.streamRequest(...).responseStream { stream in
// Process stream.
throw SomeError() // Cancels request.
}
마지막으로, Stream 값의 cancel() 메서드를 사용하여 DataStreamRequest를 취소할 수 있습니다.
AF.streamRequest(...).responseStream { stream in
// Decide to cancel request.
stream.cancel()
}
Alamofire를 이용한 토큰으로 서버에 JSON 값 요청하기
func getTest() {
do {
let token = try KeyChain.get()
let url = Constants().currentmusic!
AF.request(url,
method: .get,
parameters: nil,
headers: ["Content-Type":"application/json", "Accept":"application/json", "Authorization": token.token])
.validate(statusCode: 200..<300)
.responseDecodable(of: MusicInfoModel.self) { response in
print(response.value!)
}
} catch {
print(error)
}
}
'SwiftUI' 카테고리의 다른 글
HKWorkoutSession (0) | 2024.03.11 |
---|---|
HKHealthStore (0) | 2024.03.11 |
Authorizing access to health data (0) | 2024.03.11 |
HealthKit 을 사용하기 전에 세팅 (0) | 2024.03.11 |
HealthKit 소개 (0) | 2024.03.11 |