Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions SOOUM/SOOUM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3943,6 +3943,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 201020;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 99FRG743RX;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "SOOUM/Resources/Develop/Info-dev.plist";
Expand Down Expand Up @@ -4234,6 +4235,7 @@
CODE_SIGN_ENTITLEMENTS = "SOOUM/Resources//SOOUM.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 201020;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 99FRG743RX;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "SOOUM/Resources/Production/Info-prod.plist";
Expand Down
16 changes: 13 additions & 3 deletions SOOUM/SOOUM/Base/DIContainer/BaseDIContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ final class BaseDIContainer: BaseDIContainerable {
// 등록된 서비스의 생성 클로저(factory)를 저장하는 딕셔너리입니다.
// 키는 서비스 타입의 이름(String), 값은 Any를 반환하는 클로저입니다.
private var factories: [String: (BaseDIContainerable) -> Any] = [:]
// 한 번 생성된 객체를 보관하여 이후 resolve 요청 시 동일한 인스턴스를 반환하는 딕셔너리입니다.
private var instances: [String: Any] = [:]

/// 초기화 시 부모 컨테이너를 주입받을 수 있습니다.
/// - Parameter parent: 부모 컨테이너. nil일 경우, 최상위 컨테이너가 됩니다.
Expand All @@ -47,14 +49,22 @@ final class BaseDIContainer: BaseDIContainerable {

func resolve<Service>(_ type: Service.Type) -> Service {
let key = String(describing: type)
// 1. 현재 컨테이너에서 의존성 해결을 시도합니다.
// 1. 이미 생성되어 instances 저장소에 보관된 객체가 있는지 확인합니다.
// 객체가 존재한다면 새로운 객체를 만들지 않고 기존 객체를 반환하여 앱 전체에서 상태를 공유합니다.
if let instance = self.instances[key] as? Service {
return instance
}
// 2. 현재 컨테이너에서 의존성 해결을 시도합니다.
if let factory = self.factories[key] {
// factory는 (DIContainerProtocol) -> Any 타입을 가지므로,
// 실제 서비스 타입(Service)으로 캐스팅하여 반환합니다.
// register 함수에서 타입을 보장하므로 강제 캐스팅(!)이 안전합니다.
return factory(self) as! Service
let new = factory(self) as! Service
// 생성된 객체를 instances 저장소에 저장합니다.
self.instances[key] = new
return new
}
// 2. 현재 컨테이너에서 찾지 못했고, 부모가 있다면 부모에게 해결을 위임합니다.
// 3. 현재 컨테이너에서 찾지 못했고, 부모가 있다면 부모에게 해결을 위임합니다.
if let parent = self.parent {
return parent.resolve(type)
}
Expand Down
53 changes: 38 additions & 15 deletions SOOUM/SOOUM/Data/Managers/AuthManager/AuthManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,9 @@ extension AuthManager: AuthManagerDelegate {
*/
func reAuthenticate(_ token: Token, _ completion: @escaping (AuthResult) -> Void) {
var immediateResult: AuthResult?
/// isReAuthenticating == true로 변경되기 전 짧은 시간에 재인증 요청이 들어왔을 때, 순서 보장
let shouldRequest: Bool = self.reAuthenticateQueue.sync {
let shouldReAuthenticate: Bool = self.reAuthenticateQueue.sync { [weak self] in
guard let self = self else { return false }

guard self.authInfo.token.isEmpty == false else {
let error = NSError(
domain: "SOOUM",
Expand All @@ -252,21 +253,25 @@ extension AuthManager: AuthManagerDelegate {

self.pendingResults.append(completion)

/// 1개 이상의 API에서 reAuthenticate 요청 했을 때,
/// 처음 요청이 끝날 떄까지 대기
guard self.isReAuthenticating == false else { return false }
self.isReAuthenticating = true

return true
}
/// 여러 API가 재인증 요청 시, 맨 처음 요청만 재인증 요청
if shouldRequest == false, let result = immediateResult {

if shouldReAuthenticate == false, let result = immediateResult {
completion(result)
} else if shouldRequest {
} else if shouldReAuthenticate {

/// 여러 API가 재인증 요청 시, 맨 처음 요청만 재인증 요청
let request: AuthRequest = .reAuthenticationWithRefreshSession(token: token)
let scheduler: SerialDispatchQueueScheduler = .init(
queue: self.reAuthenticateQueue,
internalSerialQueueName: "reAuthenticate.serial.scheduler"
)
self.provider.networkManager.perform(TokenResponse.self, request: request)
.map(\.token)
.observe(on: scheduler)
.subscribe(
with: self,
onNext: { object, token in
Expand All @@ -293,13 +298,29 @@ extension AuthManager: AuthManagerDelegate {
},
onError: { object, error in
/// 재인증 과정이 실패하면 항상 재로그인 시도
object.certification()
.subscribe(onNext: { isRegistered in
object.excutePendingResults(isRegistered ? .success : .failure(error))

object.isReAuthenticating = false
})
.disposed(by: object.disposeBag)
if case 403 = (error as NSError).code {

object.certification()
.observe(on: scheduler)
.subscribe(
onNext: { isRegistered in
object.excutePendingResults(isRegistered ? .success : .failure(error))

object.isReAuthenticating = false
},
onError: { _ in
object.excutePendingResults(.failure(error))

object.isReAuthenticating = false
}
)
.disposed(by: object.disposeBag)
} else {

object.excutePendingResults(.failure(error))

object.isReAuthenticating = false
}
}
)
.disposed(by: self.disposeBag)
Expand Down Expand Up @@ -327,7 +348,9 @@ extension AuthManager {

/// success 또는 failure가 발생하면 모든 API 요청에 새로운 토큰을 적용하도록 실행
private func excutePendingResults(_ result: AuthResult) {
self.pendingResults.forEach { $0(result) }
let completions = self.pendingResults
self.pendingResults.removeAll()

completions.forEach { $0(result) }
}
}