Alamofire 코드 뜯어보기

이건준·2023년 3월 10일
0
  • 매번 네트워크 라이브러리로 인기많은것 중에 하나가 Alamofire이다. 항상 별 생각이 사용하였는데 내부적으로 어떻게 동작되는지 알아보는 시간을 가져보자
AF.request("https://github.com/dlrjswns")
  • 보통 Alamofire의 기본 사용법으로 위 코드가 종종사용된다

AF는 뭘까 ??

public let AF: Alamofire.Session

open class Session {

    /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified.
    public static let `default`: Alamofire.Session
    
public let AF = Session.default
  • Alamofire 내부 코드를 확인하면 해당 라이브러리에서는 기본적으로 Session에 대한 싱글톤 인스턴스인 default를 제공한다

즉, AF.request == Session.default.request

그렇다면 기본 제공인스턴스외에 내가 Session을 커스텀할 수 있을까 ??

  • 위 Alamofire공식문서를 확인해보면 Session을 커스텀할때는 URLSessionConfiguration.af.default를 추천한다
    -> 그 이유로는 기본적으로 헤더에 Accept-Encoding, Accept-Language, User-Agent를 제공해주기때문이다
let configuration = URLSessionConfiguration.af.default
// 커스텀 기능 추가하기
configuration.timeoutIntervalForRequest = 30 // 타임아웃 시간 30초로 설정configuration.waitsForConnectivity = true // 인터넷 연결 기다렸다가 요청
// ....
let session = Session(configuration: configuration)
session.request("https://github.com/dlrjswns")
  • 또한 우리가 API를 이용하여 request를 보낼때 Authorization, Content-Type을 헤더에 추가하곤하는데 이는 ParameterEncoder나 RequestAdapter에서 추가할것을 추천한다

timeoutInterval & waitsForConnectivity ??

  • 타임아웃인터벌은 정해진 시간동안 데이터가 오지않을 시에 오류를 발생할 수 있도록 하는 속성이다

  • waitsForConnectivity 속성은 해당 세션이 연결될때까지 기다릴것인지 기다리지않을 것인지를 결정하는 속성이다

  • 2개의 속성 모두 URLSessionConfiguration의 속성을 이용한다

RequestAdapter가 뭘까 ??

  • Alamofire에서는 RequestInterceptor라는 프로토콜이 존재한다
public protocol RequestInterceptor : Alamofire.RequestAdapter, Alamofire.RequestRetrier {
}

extension RequestInterceptor {

    /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
    ///
    /// - Parameters:
    ///   - urlRequest: The `URLRequest` to adapt.
    ///   - session:    The `Session` that will execute the `URLRequest`.
    ///   - completion: The completion handler that must be called when adaptation is complete.
    public func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: @escaping (Result<URLRequest, Error>) -> Void)

    /// Determines whether the `Request` should be retried by calling the `completion` closure.
    ///
    /// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
    /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
    /// cleaned up after.
    ///
    /// - Parameters:
    ///   - request:    `Request` that failed due to the provided `Error`.
    ///   - session:    `Session` that produced the `Request`.
    ///   - error:      `Error` encountered while executing the `Request`.
    ///   - completion: Completion closure to be executed when a retry decision has been determined.
    public func retry(_ request: Alamofire.Request, for session: Alamofire.Session, dueTo error: Error, completion: @escaping (Alamofire.RetryResult) -> Void)
}
  • 실제 이 프로토콜은 RequestAdapter와 RequestRetrier라는 2개의 프로토콜을 채택하고있다

  • 말 그대로 서버에 어떠한 요청을 보내기전에 잠시 가로채어 어떠한 동작을 수행할 수 있는 역할을 한다

  • adapt함수는 앞서말한 요청을 보내기이전 작업을 수행한다. 예를 들어서 위에서 언급한 Authorization과 같은 헤더를 추가할 수 있다

  • retry함수는 요청에 오류가 발생할때만 실행되는데 이때 보통 토큰값이 유효하지않아 재발급해줘야할때와 같은 코드가 추가될 수 있을 것이다

ParameterEncoder가 뭘까 ??

public protocol ParameterEncoder {
    /// Encode the provided `Encodable` parameters into `request`.
    ///
    /// - Parameters:
    ///   - parameters: The `Encodable` parameter value.
    ///   - request:    The `URLRequest` into which to encode the parameters.
    ///
    /// - Returns:      A `URLRequest` with the result of the encoding.
    /// - Throws:       An `Error` when encoding fails. For Alamofire provided encoders, this will be an instance of
    ///                 `AFError.parameterEncoderFailed` with an associated `ParameterEncoderFailureReason`.
    func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest
}
  • ParameterEncoder는 프로토콜로 encode함수가 존재하는데 코드에서와 같이 Parameters라는 Encodable을 채택한 모델과 request를 받아서 Encodable한 모델을 매개변수로 이용할 수 있다
open class JSONParameterEncoder: ParameterEncoder
open class URLEncodedFormParameterEncoder: ParameterEncoder

enum Router: URLRequestConvertible {
    case get([String: String]), post([String: String])
    
    var baseURL: URL {
        return URL(string: "https://httpbin.org")!
    }
    
    var method: HTTPMethod {
        switch self {
        case .get: return .get
        case .post: return .post
        }
    }
    
    var path: String {
        switch self {
        case .get: return "get"
        case .post: return "post"
        }
    }
    
    func asURLRequest() throws -> URLRequest {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.method = method
        
        switch self {
        case let .get(parameters):
            request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
        case let .post(parameters):
            request = try JSONParameterEncoder().encode(parameters, into: request)
        }
        
        return request
    }
}
  • 실제로 ParameterEncoder프로토콜을 채택한 JSONParameterEncoder와 URLEncodedFormParameterEncoder클래스에서 구현한 encode함수를 이용하여 파라미터를 request에 추가해줄 수 있다

0개의 댓글