[iOS] Alamofire

Charlie·2022년 10월 2일
1
post-custom-banner

기존 URLSession의 request

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

Alamofire는 HTTP 통신할 때 사용하는 URLSession을 조금 더 쉽게 사용할 수 있도록 도와주는 라이브러리다.
Alamofire에서는

  • chainable request, response
  • URL, JSON, plist parameter 인코딩
  • response에 대한 validation
  • response serialization
    등등 여러 기능들을 제공한다.
    따라서 Alamofire를 사용하면 데이터 접근이 비교적 수월하고 코드를 더 깔끔하고 가독성 있게 쓰는게 가능해진다.

request 메소드

Alamofire에서 request는 2가지 방법으로 가능하다. cmd + 클릭을 해서 들어가보면 설명이 아주 잘 되어있다.
참고로 Alamofire ver5 이후부터 AF가 Session.default에 대한 참조이다.

1번

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)
    }
  • convertible : url
  • method : URLRequest의 HTTPMethod. 디폴트로 GET.
  • parameters : URLRequest에 들어가는 Encodable 값들. 디폴트로 nil
  • encoder : parameters를 인코드 하는 인코더. 디폴트로 URLEncodedFormParameterEncoder.default
  • headers : URLRequest의 HTTPHeaders. 디폴트로 nil
  • interceptor : 반환되는 DataRequest의 RequestInterceptor. 디폴트로 nil
  • requestModifier : 파라미터들로 만들어지는 URLRequest에 적용될 RequestModifier. 디폴트로 nil

서버가 보안상의 이유로 인증된 사용자에게만 데이터를 줘야하는 상황에서, 어떻게 인증된 사용자인지 알 수 있을까?
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(...)

2번

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
    }

response validation

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
        }
    }

MIME 타입

response

Alamofire에서는 앞서 살펴본 request 뒤에 response를 붙여주면 된다.

AF.request(url).response()

Alamofire는 기본적으로 6개의 response handler를 가지고 있다.

1. 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)
  • response data에 대해서 검증하지 않아서 .success, .failure가 없다.
  • URLSessionDelegate로 직접 모든 정보를 전달한다.
  • URLSessionDelegate : URLSession 인스턴스가 세션 수준의 이벤트를 처리하기 위해 Delegate에서 호ㅜㄹ하는 메서드를 정의하는 프로토콜
AF.request(urlString).response { response in
	debugPrint(response)
}

2. responseData Handler

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)
}

3. responseString Handler

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)
}

4. responseJSON Handler

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을 사용하도록 하자

5. responseDecodable Handler

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)
}

download file

데이터를 메모리로 가져오는 것 말고 디스크에 파일로 다운로드를 하기 위해서 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를 사용하면 된다.

Destination closure

destinationURL로 이동하기 전에 closure에 지정된 옵션이 실행된다.

  • .createIntermediateDirectories : 지정된 경우 destination URL에 대한 intermediate directories를 만든다.
  • .removePreviousFile : 지정된 경우 destination 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(urlString, to: destination).response { response in
    debugPrint(response)

    if response.error == nil, let imagePath = response.fileURL?.path {
        let image = UIImage(contentsOfFile: imagePath)
    }
}

Reference

Alamofire Github
Alamofire란 무엇이고 어떻게 사용하는가?
iOS Swift 라이브러리 Alamofire 사용하기
[swift] Alamofire란? Alamofire 사용법 뽀개기
KeyChainSwift + Alamofire로 http header에 인증키 실어보내기
iOS) Alamofire(알라모파이어) 깃허브 문서를 요약해보자
Alamofire Tutorial (iOS 통신 라이브러리)

profile
Hello
post-custom-banner

0개의 댓글