일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- UIKit
- Moya
- defaultContentConfiguration
- iOS교육
- 라이징캠프
- RxSwift
- dispatchqueue
- UICollectionViewListCell
- QoS
- URLSession
- ReactiveX
- UIListContentConfiguration
- 컴공선배
- observe(on:)
- alamofire
- Main Thread
- ContentMode
- restfulAPI
- SWIFT
- observable
- flatmap
- Xcode
- interceptor
- distinctUntilChanged
- RequestInterceptor
- 개발블로그
- SwiftUI
- RxCocoa
- IOS
- cellForRowAt
- Today
- Total
RB의 iOS 개발 이야기
Alamofire RequestInterceptor 사용해보기 본문
로그인 같은 작업을 수행하게되면 대개 로그인 API에서 accessToken과 refreshToken을 반환하게된다.
이러한 토큰들은 앱 내의 여러 작업을 수행하는 API를 요청할 때에 필요한 값들인데
오늘은 회원탈퇴를 구현하는 부분에서 구현하는데에 시간이 꽤 많이 소요된 부분을 정리하고자 한다.
회원탈퇴를 요청할 때에 내가 사용중인 API는 요청 바디는 없으며 요청 헤더로 응답받은 accessToken과 고유 key를 넣게된다.
회원탈퇴 API를 요청하는 부분에서 고려해야할 상황이 있는데 로그인 후 앱을 사용하다가 token이 만료되는 상황이 있을 수 있다는 것이다.
대개 흔치 않은 경우지만 개발자는 사용자의 모든 경우의 수를 생각하고 개발해야 하기에 회원탈퇴 시 token이 만료되는 상황을 고려해보았고 코드를 짜봤다.
회원탈퇴 시 token이 만료되어 있다면 어떻게 해야할까?
결론을 먼저 얘기하자면 accessToken이 만료됐다는 statusCode가 떨어지면 그 때 accessToken을 갱신해주는 API를 호출하고 accessToken을 다시 저장한 다음 회원탈퇴 API를 호출하면된다.
- 회원탈퇴 API 호출
- token이 만료되었다는 statusCode 방출 (ex. 419)
- 419 code를 확인하고 token 갱신 API 호출
- token API의 statusCode가 정상적으로 200으로 떨어지면 token 저장
- 저장된 토큰을 가지고 다시 한번 더 회원탈퇴 API 호출
- 갱신된 토큰이기에 회원탈퇴 진행 (회원탈퇴 API를 호출하면서 일어나는 error는 token 갱신과 별개)
생각보다 복잡한(?) 위의 작업들을 Alamofire에서 RequestInterceptor라는 프로토콜로 제공해주고있다.
단어 그대로 intercept, 중간에 낚아채어 기능을 수행해주는 것이라고 생각하면 된다.
먼저 RequestInterceptor 프로토콜을 채택한 SeSACRequestInterceptor을 생성한다.
아래의 코드를 확인해보자.
import Foundation
import Alamofire
import RxSwift
final class SeSACRequestInterceptor: RequestInterceptor {
static let shared = SeSACRequestInterceptor()
private init() { }
let repository = NetworkRepository()
let disposeBag = DisposeBag()
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
guard urlRequest.url?.absoluteString.hasPrefix(APIKey.sesacURL) == true else {
completion(.success(urlRequest))
return
}
var urlRequest = urlRequest
urlRequest.setValue(UserDefaultsManager.token, forHTTPHeaderField: "Authorization")
urlRequest.setValue(UserDefaultsManager.refreshToken, forHTTPHeaderField: "Refresh")
urlRequest.setValue(APIKey.sesacKey, forHTTPHeaderField: "SesacKey")
print("adator 적용 \(urlRequest.headers)")
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 419 else {
completion(.doNotRetryWithError(error))
return
}
let task = Observable.just(())
task
.observe(on: MainScheduler.asyncInstance)
.flatMap { _ in
self.repository.requestAccessToken()
}
.subscribe(onNext: { result in
switch result {
case .success(let data):
print(UserDefaultsManager.token)
UserDefaultsManager.token = data.token
completion(.retry)
case .failure(let error):
completion(.doNotRetryWithError(error))
}
})
.disposed(by: disposeBag)
}
}
adapt
코드가 꽤 길지만 adapt 메서드부터 차근히 보겠습니다.
먼저 adapt 메서드의 역할은 네트워크 요청을 진행할 때 요청하기 전 adapt 메서드 내부의 코드와 같이 헤더의 값을 넣어주는 기능을 수행하고 있습니다.
보통 API 요청 시 body의 값들은 API마다 상이한 편이며 header의 경우 비슷한 값이 들어가는 경우가 종종 있습니다.
저는 adapt 내에 3개의 header 값을 넣어주는 코드를 작성하였습니다.
여기서 Tip)
저도 코드를 작성하기까지 몰랐던 사실인데 다른 수강생분을 통해 알게된 것으로 header는 API 요청에 필요한 header 값 이외의 값이 추가적으로 들어가 있어도 API를 요청하는데에는 문제가 없음을 알게 되었습니다. 그러므로 제 코드에서도 만약 API를 요청하는데 요청하는 API의 header 값에 token, refreshToken, SesacKey가 필요없더라도 요청하는데에 문제는 발생하지 않는다는 얘기입니다..!!
본인이 Alamofire를 사용하며 Router 패턴을 직접 작성하여 Header를 넣어주고 있다면 adapt에 작성할 내용이 저와는 다를 것이고 또한 Moya를 사용하시는 분들도 본인의 코드에 맞게 adpat 메서드 내부는 다르게 작성하실 수 있을 것 입니다.
(저는 Moya를 사용하고 있지만 adapt 내부에 3개의 header 값을 넣어주었습니다 ㅎㅎ)
retry
다음은 retry 입니다.
retry는 말 그대로 다시 시도하는 메서드라고 볼 수 있습니다.
adapt에서 header를 추가하여 retry를 시도합니다.
하지만 우리는 항상 retry를 시도하면 안되겠죠? 이 글을 작성하는 이유도 이 부분을 공유하기 위해 작성한 것이죠.
token이 만료되었을 때에만 retry를 시도하는 것 입니다.
task라는 observable은 void 값의 이벤트를 단일 이벤트로 방출하고 있고 그것을 구독하여 flatMap 내부에서 AccessToken 갱신 API를 요청하고 있습니다.
API 요청이 성공하면 UserDefaults에 덮어쓰는 것이고 실패하면 에러 방출 아니겠습니까
interceptor 메서드 내 코드 작성은 위의 설명으로 말씀 드릴 수 있을 것 같고
마지막으로 Moya를 사용하신다면 추가적으로 수정해줘야 interceptor를 사용할 수 있습니다.
요거까지 공유드리고 마쳐보려고 합니다 ㅎㅎ
final class APIManager: NetworkService {
static let shared = APIManager()
private init() { }
private let provider = MoyaProvider<SeSACAPI>(session: Moya.Session(interceptor: SeSACRequestInterceptor.shared))
// private let provider = MoyaProvider<SeSACAPI>()
func request<T: Decodable>(target: SeSACAPI) -> Single<NetworkResult<T>> {
return Single<NetworkResult<T>>.create { [weak self] (single) -> Disposable in
guard let self else { return Disposables.create() }
self.provider.request(target) { result in
...
}
}
}
}
Moya를 사용하시면 provider 클래스 인스턴스를 생성해주어야 하는데
위 코드처럼 주석처리 되어있는 부분이 기본적으로 생성하는 방법이고
우리는 이제 session에 custom Session을 넣어주어야합니다!
요렇게까지 수정해주시면 Alamofire의 RequestInterceptor을 경험해보실 수 있을겁니다. 오늘은 여기까지!
+ More...)
Rx에서 지원하는 모든 종류의 스케쥴러에 대한 추가적인 정보를 다음 포스트에서 공유하겠습니다...!
스케쥴러와 RequestInterceptor의 연관성이 뭔지는 다음 포스트에서! 감사합니다~!
'iOS > RxSwift' 카테고리의 다른 글
Rx가 지원하는 스케줄러(schedulers)의 종류들 (0) | 2024.01.07 |
---|---|
App Store 검색 예제 구현중 발생한 메모리 누수 이슈 (0) | 2023.11.28 |
Observable과 Subject의 차이는 무엇인가? (0) | 2023.10.27 |