diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index 16c57433..a1a6dc42 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -277,7 +277,7 @@ CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Dev.entitlements"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; GENERATE_INFOPLIST_FILE = YES; @@ -294,11 +294,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.2; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.heartz.ByeBoo-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -317,9 +317,9 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Prod.entitlements"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = Z6682N5G5D; GENERATE_INFOPLIST_FILE = YES; @@ -336,11 +336,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.2; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.heartz.ByeBoo-iOS"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -480,7 +480,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.2; MACOSX_DEPLOYMENT_TARGET = 15.2; @@ -502,7 +502,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.2; MACOSX_DEPLOYMENT_TARGET = 15.2; diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/TokenService.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/TokenService.swift index 97d46269..2bfd6126 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/TokenService.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/TokenService.swift @@ -9,20 +9,37 @@ import Foundation import Alamofire -protocol TokenService { +protocol TokenService: Sendable { func reissue() async throws } -final class DefaultTokenService: TokenService { + +actor DefaultTokenService: TokenService { + private var tokenTask: Task? private let keychainService: KeychainService - init( - keychainService: KeychainService - ) { + + init(keychainService: KeychainService) { self.keychainService = keychainService } func reissue() async throws { + + if let task = tokenTask { + return try await task.value + } + + let task = Task { + try await fetchAuthReissue() + } + + tokenTask = task + defer { tokenTask = nil } + + return try await task.value + } + + private func fetchAuthReissue() async throws { let header: HeaderType = .withAuth(acessToken: keychainService.load(key: .refreshToken)) ByeBooLogger.debug("토큰 재발급 시작") @@ -39,6 +56,8 @@ final class DefaultTokenService: TokenService { .validate() .responseDecodable(of: BaseResponse.self) { [weak self] response in guard let self else { return } + ByeBooLogger.debug("💡Reissue \(response)") + switch response.result { case .success(let data): guard let data = data.data else { @@ -46,39 +65,44 @@ final class DefaultTokenService: TokenService { continuation.resume(throwing: ByeBooError.noData) return } - ByeBooLogger.debug("토큰 재발급 완료") - self.keychainService.save(key: .accessToken, token: data.accessToken) - self.keychainService.save(key: .refreshToken, token: data.refreshToken) - continuation.resume(returning: ()) - case .failure(let error): - ByeBooLogger.debug("토큰 재발급 실패, 키체인 삭제 후 로그인으로 이동") - self.clearKeychain() - DispatchQueue.main.async { - NotificationCenter.default.post(name: .navigateLoginViewController, object: nil) + + Task { + await self.debugTokenReissueSuccess(data) + continuation.resume(returning: ()) } - if let data = response.data, - let statusCode = response.response?.statusCode, - let errorResponse = try? JSONDecoder().decode(EmptyResponse.self, from: data) { - ByeBooLogger.error(error) + + case .failure(let error): + Task { + await self.debugTokenReissueFailure() continuation.resume(throwing: error) - } else { - ByeBooLogger.error(ByeBooError.decodingError) - continuation.resume(throwing: ByeBooError.decodingError) } } } } } -} - -extension DefaultTokenService { + + private func debugTokenReissueSuccess(_ data: TokenReissueResponseDTO) { + ByeBooLogger.debug("토큰 재발급 완료") + self.keychainService.save(key: .accessToken, token: data.accessToken) + self.keychainService.save(key: .refreshToken, token: data.refreshToken) + } + + private func debugTokenReissueFailure() { + ByeBooLogger.debug("토큰 재발급 실패, 키체인 삭제 후 로그인으로 이동") + clearKeychain() + DispatchQueue.main.async { + NotificationCenter.default.post(name: .navigateLoginViewController, object: nil) + } + } + private func clearKeychain() { for key in KeyType.allCases { let token = keychainService.load(key: key) - if !token.isEmpty { - keychainService.delete(key: key) - ByeBooLogger.debug("\(key) 삭제") + if !token.isEmpty { + keychainService.delete(key: key) + ByeBooLogger.debug("\(key) 삭제") } } } + } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift index c6d2c9b3..985db17f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift @@ -98,7 +98,7 @@ struct DefaultAuthRepository: AuthInterface { func autoLogin() async throws -> Bool { let isOnboardingCompleted: Bool = userDefaultsService.load(key: .isOnboardingCompleted) ?? false - ByeBooLogger.debug(isOnboardingCompleted) + ByeBooLogger.debug("온보딩 여부 \(isOnboardingCompleted)") if !keychainService.load(key: .accessToken).isEmpty && !keychainService.load(key: .refreshToken).isEmpty @@ -214,7 +214,7 @@ final class MockAuthRepository: AuthInterface { func appleLogin(platform: LoginPlatform) async throws { appleLoginCalled = true - var (identityToken, authorizationCode) = try await network.appleRequest() + let (identityToken, authorizationCode) = try await network.appleRequest() let _ = userDefaultsService.save("APPLE", key: .loginPlatform) keychainService.save(key: .authorization, token: identityToken) keychainService.save(key: .authorizationCode, token: authorizationCode) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift index 7e145e2d..2f319175 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/LoginViewController.swift @@ -43,12 +43,14 @@ final class LoginViewController: BaseViewController { extension LoginViewController{ @objc private func kakaoLoginButtonDidTap() { + rootView.kakaoLoginButton.isUserInteractionEnabled = false self.platform = .KAKAO viewModel.action(.socialLoginButtonDidTap(platform: .KAKAO)) } @objc private func appleLoginButtonDidTap() { + rootView.appleLoginButton.isUserInteractionEnabled = false self.platform = .APPLE viewModel.action(.socialLoginButtonDidTap(platform: .APPLE)) } @@ -57,12 +59,15 @@ extension LoginViewController{ extension LoginViewController { private func bind() { viewModel.output.isRegisteredPublisher - .throttle(for: .seconds(1.0), scheduler: DispatchQueue.main, latest: false) .receive(on: DispatchQueue.main) .sink { result in + self.rootView.appleLoginButton.isUserInteractionEnabled = true + self.rootView.kakaoLoginButton.isUserInteractionEnabled = true + switch result { case .success(let isRegisterd): - ByeBooLogger.debug(isRegisterd) + ByeBooLogger.debug("온보딩 완료 여부 \(isRegisterd)") + let nextViewController: UIViewController if isRegisterd { nextViewController = ByeBooTabBar() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift index a14cef3d..bbfc1993 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift @@ -64,6 +64,7 @@ extension SplashViewModel { guard let error = error as? ByeBooError else { return } + autoLoginSubject.send(.failure((.noData))) ByeBooLogger.debug(ByeBooError.networkConnect) ByeBooLogger.error(error as ByeBooError) }