[swift] Alamofire - Custom Session, API 인증(Authentication), 로그(Logger) 구현하기

Yungso
9 min readSep 15, 2021

--

이 글에선 Alamofire Advanced Usage 중에 Creating Custom Session InstanceAdapting and Retrying Requests with RequestInterceptor, Using EventMonitors에 관해 알아보고 구현해봅니다.

Alamofire session을 이용하면 서로 다른 request들에 공통 기능을 적용할 수 있습니다. 엄청나지 않나요? 왜, 서로 다른 request이더라도 특정 에러 코드에 대해 공통적으로 처리해줘야 할때가 있잖아요. session을 이용하면 쉽게 가능합니다. 이번 글에선 바로 이 session을 이용해서 아래와 같은 것을 소개합니다.

  • 커스텀 세션 생성 - URLSessionConfiguration
  • API 인증 실패 시, 토큰 갱신 후 재시도 - ReqeustInterceptor
  • API 로그찍기 - EventMonitor

코드는 Alamofire 5.2버전 기준이고, 전체 코드 저장소는 글 맨 아래 있습니다.

Custom Session

Alamofire 요청의 가장 기본적인 형태는 이러하죠.⤵️

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

그리고 session을 사용하면 아래 같은 형태가 돼요. sessionsingleton으로 default 인스턴스를 제공하는데, 이는 AF와 동일해서 아래와 위 두 구문은 같은 것입니다.

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

그런데 우린 기본 session 사용할 것이 아니죠. 커스텀 session은 아래와 같이 생성합니다.⤵️

let configuration = URLSessionConfiguration.af.default// 커스텀 기능 추가하기
configuration.timeoutIntervalForRequest = 30 // 타임아웃 시간 30초로 설정configuration.waitsForConnectivity = true // 인터넷 연결 기다렸다가 요청
// ....
let session = Session(configuration: configuration)
session.request("https://httpbin.org/get")

이제부터 본격적으로 커스텀 ReqeustInterceptorEventMonitor를 만들어서 session에 적용해볼 건데요, 그 전에 전에 session에 대해 알아두면 좋을 것들이 있어 Alamofire 문서 글을 적어두겠습니다.

Session을 커스텀할 때 URLSessionConfiguration.af.default instance로 시작하는걸 추천한다. 이는 헤더에 기본값들을 설정되어있는데, 기본값 설정된 헤더는 Accept-Encoding, Accept-Language, User-Agent이다. 기본값 없이 URLSessionConfiguration을 사용하는 것도 물론 가능하다.

Authorization이나 Content-Type의 헤더는 URLSessionConfiguration에서 설정하는걸 추천하지 않는다. ParameterEncoders 혹은 ReqeustAdapter를 사용해서 Reqeust에 추가하길 바란다.

RequestInterceptor로 API 인증하기

인터셉터.. 이름에서 느껴지듯, 이놈은 요청을 가로채서 헤더를 추가하고 에러가 났을 때 재요청을 할 수 있게 해줍니다. ReqeustInterceptor는 두 개 프로토콜이 합쳐진 것입니다. ⤵️

1. ReqeustAdaptor: 요청되기 전에 Reqeust를 점검하고 변경할 수 있게 한다. (여기에 Authorization 헤더를 추가해줄 거예요.)2. ReqeustRetrier: Reqesut에 에러가 발생했을 때, 재시도할 수 있게 해준다.

그럼 ReqeustInterceptor 구현해볼까요

[코드 노트]

1. retryLimit&retryDelay: 재시도하는 횟수를 정해주거나, 재시도할 때 딜레이를 주려면 이 값을 설정해주세요. ( 예제에서는 사용하지 않아서 0으로 했어요.)

2. adapt(_:for:completion:): ReqeustAdaptor 프로토콜의 메소드에요. 이 메소드는 네트워크를 통해 요청되기 전에 실행됩니다. 여기서 Aurhorization 헤더를 추가해줬습니다.

3. retry(_:for:dueTo:completion:): ReqeustRetrier 프로토콜의 메소드에요. 이 메소드는 요청에 에러가 발생했을 때만 실행돼요. 이 메소드에서 completionRetryResult로 재시도를 할 것인지 설정해줘야 합니다. RetryResult 정의는 아래 첨부하니, 확인해주세요. ⏬

위 코드에선, 401에러일때만 토큰 갱신 후 재시도하고 있습니다. 여기서 갱신된 토큰이 제대로 저장해줘야합니다. 그래야지 adapt(_:for:completion:) 메소드가 다시 호출됐을 때, 갱신된 토큰으로 적용되기 때문이에요. refreshToken() 함수도 API 명세에 맞게 작성해주세요🤓

public enum RetryResult {
case retry // 즉시 재시도
case retryWithDelay(TimeInterval) // `TimeInterval`후에 재시도
case doNotRetry // 재시도 하지 않음
case doNotRetryWithError(Error) // `Error`로 재시도하지 않음
}

EventMonitor로 API 로그 찍기

EventMonitor는 Alamofire의 이벤트를 받을 수 있게 해줍니다. 그래서 로깅에 가장 많이 사용된다고 합니다.

[코드 노트]

1. DispatchQueue: 모든 이벤트를 저장하는 DispatchQueue가 필요합니다. 성능을 위해 메인 큐(DispatchQueue.main)가 아닌, 별도 serial queue 를 만드는 것을 추천하고 있습니다.

2. requestDidFinish(_:): 요청이 끝났을 때 호출되는 이 메소드에선 요청 내용을 출력합니다.

여기서 print(request.description)는 “httpMethod URL (statusCode)”순서로 콘솔에 출력해줍니다.

추가로, 헤더나 바디에 대한 정보도 필요하면 출력해줍니다. 이는 API 요청에 에러가 났을 땐 원인 파악에 유용하죠.

3. request(_:didParseResponse:)는 응답을 받았을 때 호출됩니다. result, statusCode 그리고 JSON 데이터를 JSONSerialization으로 예쁘게 출력해줍니다.

toPrettyPrintedString()Data extension으로 정의해서 사용할 수 있습니다. 🔽

Session에 RequestInterceptor와 EventMonitor 적용해주기

마지막으로 session에 적용해주겠습니다. sessionsingleton으로 해서 전역에서 접근할 수 있습니다. ⤵️

import Alamofireclass APIManager {  static let shared = APIManager()  let session: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
let apiLogger = APIEventLogger()
let interceptor = AuthInterceptor()

return Session(
configuration: configuration,
interceptor: interceptor,
eventMonitors: [apiLogger])
}()
}

실제로 요청할 때는 이렇게 사용합니다. ⤵️

APIManager.shared.session.request("https://httpbin.org/get")

마무리

저는 ReqeustInterceptor를 알기 전에 이 로직을 직접 구현했었습니다. 어휴 상상만 해도 코드가 긴데요, 토큰 에러가 나면 토큰을 갱신하고, 새로 받은 토큰으로 Reqeust header를 업데이트해주고… 거의 100줄 정도 됐던 코드를 ReqeustInterceptor로 깔끔하게 구현할 수 있었네요. Alamofire 5.2에서는 AuthenticationInterceptor로 OAuth 구현하는 새로운 기능이 나왔습니다. ReqeustInterceptor와 가장 눈에 띄는 다른점은 ‘Reqeust전에’ 토큰 만료 시간을 확인하고 토큰을 갱신할 수 있다는 점입니다. 하지만 아직 예제가 많지 않아 충분한 테스트 후에 적용하는 것이 좋을듯합니다.

Router로 API 요청하기에 이어, Alamofire Advanced Usage의 두 번째 글을 작성해 보았습니다. 글 쓰는 건 힘들지만 박수는 큰 힘이 됩니다~👏👏👏👏

🔽 전체 소스코드를 포함한 데모 프로젝트 입니다.

[참고]

Alamofire Advanced Usage Document - Alamofire

Alamofire Tutorial for iOS: Advanced Usage - raywenderlich

--

--

Responses (1)