[swift] Alamofire - Custom Session, API 인증(Authentication), 로그(Logger) 구현하기
이 글에선 Alamofire Advanced Usage 중에 Creating Custom Session Instance와 Adapting 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
을 사용하면 아래 같은 형태가 돼요. session
은 singleton
으로 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")
이제부터 본격적으로 커스텀 ReqeustInterceptor
와 EventMonitor
를 만들어서 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
프로토콜의 메소드에요. 이 메소드는 요청에 에러가 발생했을 때만 실행돼요. 이 메소드에서 completion
의 RetryResult
로 재시도를 할 것인지 설정해줘야 합니다. 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
에 적용해주겠습니다. session
을 singleton
으로 해서 전역에서 접근할 수 있습니다. ⤵️
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의 두 번째 글을 작성해 보았습니다. 글 쓰는 건 힘들지만 박수는 큰 힘이 됩니다~👏👏👏👏
🔽 전체 소스코드를 포함한 데모 프로젝트 입니다.
[참고]