[iOS] Alamofire - Advanced Usage

김범수·2022년 6월 2일
0

iOS

목록 보기
6/8

Alamofire - Advanced Usage


충전했’오' 프로젝트에서 Access Token 뿐만이 아닌 Refresh Token을 도입하여 Authorization을 진행 및 Time-Out에 대한 처리에 대해 Research를 진행하던 도중 Alamofire에서 제공하는 **RequestInterceptor, Retrier 를 이용하기 위해 이 게시물을 작성합니다

(Alamofire - Advanced Usage 를 기반으로 작성하였음)

Session


**RequestInterceptor, Retrier 를 살펴보기 전 Session이라는 개념을 먼저 살펴 볼 필요가 있습니다. 보통 Alamofire에서 request를 생성할 때 아래와 같은 코드를 많이 작성합니다

AF.request("https://httpbin.org/get")

AF는 Session의 Singleton instance인 default의 top-level name space로 위 코드는 아래의 코드와 같은 의미를 나타냅니다.

let session = Session.default
session.request("https://httpbin.org/get")

그렇다면 Session을 사용하는 경우는 언제일까요?!

  • URLSession의 configuration을 default가 아닌 다른 값으로 설정하는 경우
let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false

let session = Session(configuration: configuration)
  • Background Session을 만드는 경우
let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")

let session = Session(rootQueue: rootQueue,
                      requestQueue: requestQueue,
                      serializationQueue: serializationQueue)
  • Session Delegate를 설정하여 콜백 함수들을 처리하는 경우
let delegate = SessionDelegate(fileManager: .default)

...

위 예시처럼 custom한 request를 생성하고자 원한다면 convenience initializer을 이용하면 됩니다.

public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
                        delegate: SessionDelegate = SessionDelegate(),
                        rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
                        startRequestsImmediately: Bool = true,
                        requestQueue: DispatchQueue? = nil,
                        serializationQueue: DispatchQueue? = nil,
                        interceptor: RequestInterceptor? = nil,
                        serverTrustManager: ServerTrustManager? = nil,
                        redirectHandler: RedirectHandler? = nil,
                        cachedResponseHandler: CachedResponseHandler? = nil,
                        eventMonitors: [EventMonitor] = [])

RequestInterceptor


RequestInterceptor는 RequestAdaptor와 RequestRetrier의 성격을 가지고 있는 protocol입니다.

RequestAdaptor

adapt method는 request 전 특정 작업을 먼저 수행이 가능합니다
아래 code는 oAuth 인증 시스템을 구현 시 header에 bearer token를 추가하는 과정입니다

func adapt(_ urlRequest: URLRequest, for _: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
    guard let accessToken = KeychainWrapper.standard.string(forKey: "accessToken") else {
        completion(.success(urlRequest))
        return
    }

    var urlRequest = urlRequest
    urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
    completion(.success(urlRequest))
}

RequestRetrier

retry method는 api response에 대해 validation method를 통해 error로 분류된 response에 대해 retry여부를 결정합니다.
아래 code는 13(timeout), 401(unauthorized)에 대해 재시도를 수행하는 code입니다

13(timeout) : 2회 재시도

401(unauthorized): refresh token으로 access token 재발급

api 호출에서 공통적으로 처리 가능한 오류에 대해 retry method에서 처리 또한 가능할 것으로 보입니다.

func retry(_ request: Request, for _: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        guard let response = request.task?.response as? HTTPURLResponse else {
            completion(.doNotRetryWithError(error))
            return
        }

        switch response.statusCode {
        case 13: // timeout
            if request.retryCount < limit {
                completion(.retry)
            } else {
                Singleton.shared.presentToastAlert.onNext(.network)
                completion(.doNotRetry)
            }
        case 401: // unauthorized
            AuthAPI.shared.refreshAuthentication()
                .subscribe(onSuccess: { result in
                    switch result {
                    case let .success(data):
                        data.refreshAccessToken()
                        completion(.retry)
                    case let .failure(error):
                        Singleton.shared.unauthorized.onNext(())
                    }
                })
                .disposed(by: bag)

        default:
            completion(.doNotRetry)
        }
    }
  • RetryResult: 재시도하는 유형에 대한 값
public enum RetryResult {
    /// Retry should be attempted immediately.
    case retry
    /// Retry should be attempted after the associated `TimeInterval`.
    case retryWithDelay(TimeInterval)
    /// Do not retry.
    case doNotRetry
    /// Do not retry due to the associated `Error`.
    case doNotRetryWithError(Error)
}
profile
iOS Developer

0개의 댓글