-
Notifications
You must be signed in to change notification settings - Fork 1
[#302] AuthService 도입 및 SceneDelegate 책임 분리 코드 리팩토링 #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @Hrepay, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a new AuthService to centralize authentication logic, including token management, refresh, and logout. It also refactors the SceneDelegate to dynamically manage the root view controller based on the authentication state, ensuring a consistent user experience for login and logout flows.
Highlights
- Centralized Authentication: A new AuthService class is introduced to consolidate all authentication-related operations, such as token validation, refreshing, and user logout. This replaces scattered authentication logic across various view controllers.
- Dynamic Root View Controller Management: The SceneDelegate is refactored to observe the authentication state provided by AuthService. This allows the application's root view controller to automatically switch between the login and home screens based on whether a user is authenticated or not.
- Unified Logout and Session Management: All logout and session expiration scenarios across the application now leverage the AuthService to ensure consistent token invalidation and redirection to the login screen, often accompanied by a toast message.
- Improved Token Handling: TokenManager and TokenRefresher are updated to integrate seamlessly with the new AuthService, ensuring robust token refresh mechanisms.
- Centralized Toast Messages: BaseViewController now includes logic to observe logout messages from AuthService and display them as toast notifications, streamlining UI feedback for authentication events.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Code once tangled, now so clean,Auth flows central, a new scene.Logic clear, serene.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
이번 PR은 인증 로직을 AuthService로 통합하고 SceneDelegate의 책임을 분리하는 중요한 리팩토링을 수행했습니다. 전반적으로 인증 흐름이 중앙에서 관리되도록 개선되어 코드의 유지보수성과 안정성이 크게 향상되었습니다. 몇 가지 개선점을 제안합니다. UserInfoService에서 try! 사용으로 인한 잠재적 크래시 위험을 수정하고, 일부 뷰 컨트롤러에서 중복되는 화면 전환 로직을 제거하여 SceneDelegate의 역할을 명확히 하는 것이 좋습니다. 또한, 디버깅용 print 구문 제거와 코드 중복 개선을 통해 코드 품질을 더욱 높일 수 있습니다.
| let realm = try! Realm() | ||
| guard let user = realm.objects(UserInfo.self).first else { | ||
| print("[UserInfoService] 백그라운드에서 user 조회 실패") | ||
| return | ||
| } | ||
|
|
||
| try! realm.write { | ||
| user.nickname = nickname | ||
| } | ||
|
|
||
| print("[UserInfoService] 닉네임 업데이트 완료 (Realm 백그라운드)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realm 인스턴스 생성 및 쓰기 트랜잭션에서 try!를 사용하고 있습니다. 디스크 공간 부족, 권한 문제 등 예기치 않은 상황에서 Realm 작업이 실패하면 앱이 강제 종료될 수 있습니다. do-catch 구문을 사용하여 오류를 안전하게 처리하고, 실패 시 로그를 남기는 등의 방어적인 코드를 작성하는 것이 좋습니다.
do {
let realm = try Realm()
guard let user = realm.objects(UserInfo.self).first else {
print("[UserInfoService] 백그라운드에서 user 조회 실패")
return
}
try realm.write {
user.nickname = nickname
}
print("[UserInfoService] 닉네임 업데이트 완료 (Realm 백그라운드)")
} catch {
print("[UserInfoService] Realm 업데이트 실패: \(error.localizedDescription)")
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 수정이 필요해보입니다.
| ) | ||
| let confirm = UIAlertAction(title: "확인", style: .default) { _ in | ||
| AuthService.shared.logout(message: nil) | ||
| self.navigateToLogin() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 수정해야합니다.
| ) | ||
| let confirm = UIAlertAction(title: "확인", style: .default) { _ in | ||
| AuthService.shared.logout(message: nil) | ||
| self.navigateToLogin() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것도 수정해야해요.
| print("[AuthService] login() 호출됨") | ||
| RealmService.shared.addToken(accessToken: accessToken, refreshToken: refreshToken) | ||
| logoutMessageRelay.accept(nil) | ||
| relay.accept(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 토스트 메시지 중복 처리 때문에 그런건데 수정 가능할 것 같습니다.
| } | ||
|
|
||
| final class TokenManager { | ||
| class TokenManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if let nickname = info.nickname { | ||
| // 사용자의 닉네임을 업데이트하고 홈 화면으로 이동 | ||
| if let currentUserInfo = UserInfoManager.shared.getCurrentUserInfo() { | ||
| UserInfoManager.shared.updateNickname(for: currentUserInfo, nickname: nickname) | ||
| print("[디버깅] 닉네임 존재함: \(nickname)") | ||
|
|
||
| let currentUser = UserInfoManager.shared.getCurrentUserInfo() | ||
| print("[디버깅] currentUserInfo: \(String(describing: currentUser))") | ||
|
|
||
| if let u = currentUser { | ||
| UserInfoManager.shared.updateNickname(for: u, nickname: nickname) | ||
| print("[디버깅] 닉네임 업데이트 완료") | ||
| } else { | ||
| print("[디버깅] currentUserInfo가 nil임") | ||
| } | ||
| changeIntoHomeViewController() | ||
| } else { | ||
| // 닉네임 설정이 필요한 경우 | ||
| print("[디버깅] 닉네임이 없음 → 닉네임 설정 화면으로 이동") | ||
| let setNicknameVC = SetNickNameViewController() | ||
| navigationController?.pushViewController(setNicknameVC, animated: true) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
디버깅 목적으로 추가된 print 구문들이 남아있습니다. 머지 전에는 삭제하는 것이 좋습니다.
if let nickname = info.nickname {
if let currentUser = UserInfoManager.shared.getCurrentUserInfo() {
UserInfoManager.shared.updateNickname(for: currentUser, nickname: nickname)
}
} else {
let setNicknameVC = SetNickNameViewController()
navigationController?.pushViewController(setNicknameVC, animated: true)
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그 보면서 작업해서 print 문들은 아직 다 그대로 냅뒀습니다!
| if self.menuIDList == nil { | ||
| let vc = SetRateViewController() | ||
| self.menuIDList = [self.menuID] | ||
| vc.dataBind(list: self.menuNameList, | ||
| idList: self.menuIDList ?? [], | ||
| reviewList: nil, | ||
| currentPage: 0) | ||
| self.activityIndicatorView.stopAnimating() | ||
| self.navigationController?.pushViewController(vc, animated: true) | ||
| } else if self.menuIDList?.count == 1 { | ||
| let vc = SetRateViewController() | ||
| vc.dataBind(list: self.menuNameList, | ||
| idList: self.menuIDList ?? [], | ||
| reviewList: nil, | ||
| currentPage: 0) | ||
| self.activityIndicatorView.stopAnimating() | ||
| self.navigationController?.pushViewController(vc, animated: true) | ||
| } else { | ||
| // 고정메뉴이고, 메뉴가 1개일때 선택창으로 안가고 바로 작성창으로 가도록 | ||
| if menuIDList?.count == 1 { | ||
| let setRateViewController = SetRateViewController() | ||
| setRateViewController.dataBind(list: menuNameList, | ||
| idList: menuIDList ?? [], | ||
| reviewList: nil, | ||
| currentPage: 0) | ||
| activityIndicatorView.stopAnimating() | ||
| navigationController?.pushViewController(setRateViewController, animated: true) | ||
| } else { | ||
| let choiceMenuViewController = ChoiceMenuViewController() | ||
| choiceMenuViewController.menuDataBind(menuList: menuNameList, idList: menuIDList ?? []) | ||
| activityIndicatorView.stopAnimating() | ||
| navigationController?.pushViewController(choiceMenuViewController, animated: true) | ||
| } | ||
| let vc = ChoiceMenuViewController() | ||
| vc.menuDataBind(menuList: self.menuNameList, idList: self.menuIDList ?? []) | ||
| self.activityIndicatorView.stopAnimating() | ||
| self.navigationController?.pushViewController(vc, animated: true) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.menuIDList == nil인 경우와 self.menuIDList?.count == 1인 경우의 로직이 중복됩니다. menuIDList가 nil일 때 값을 할당한 후, count가 1인지 확인하는 방식으로 코드를 통합하여 더 간결하게 만들 수 있습니다.
if self.menuIDList == nil {
self.menuIDList = [self.menuID]
}
if self.menuIDList?.count == 1 {
let vc = SetRateViewController()
vc.dataBind(list: self.menuNameList,
idList: self.menuIDList ?? [],
reviewList: nil,
currentPage: 0)
self.activityIndicatorView.stopAnimating()
self.navigationController?.pushViewController(vc, animated: true)
} else {
let vc = ChoiceMenuViewController()
vc.menuDataBind(menuList: self.menuNameList, idList: self.menuIDList ?? [])
self.activityIndicatorView.stopAnimating()
self.navigationController?.pushViewController(vc, animated: true)
}|
해당 PR은.. 일단 냅두다가 정리하겠습니다 ㅎㅎ |
#️⃣ 관련 이슈
Resolved #302
💡작업 내용
변경 사항이 많아서 파일 이름 위주로 설명 드리겠습니다.
인증 로직 통합 및 AuthService 중심 구조 도입
기존에 각
ViewController또는 네트워크 요청 전 분산되어 있던 토큰 검증, 재발급, 로그아웃 처리 등을AuthService에 통합.AuthService.shared.checkAndRefreshTokenIfNeeded()호출 방식으로 통일.인증 상태를 Rx 기반 상태로 관리하도록 설계.
화면 전환 흐름 정비 (루트 변경 / 자동 로그아웃 등)
SceneDelegate 또는 AppDelegate에서 AuthService의 인증 상태를 구독하여 자동 전환 처리.
accessToken, refreshToken 만료 시 루트 뷰 전환을 통해 로그인 화면으로 안전하게 복귀하도록 개선.
ViewController 인증 체크 도입
다음
ViewController들에서AuthService기반의 유효성 체크 로직 추가:HomeViewController,MyPageViewController,MyReviewViewController,UserWithdrawViewController,ReviewViewController,SetRateViewController,LoginViewController,SetNickNameViewController기존 BaseViewController, SceneDelegate 등 구조 정비
BaseViewController에 각 vc에서 쓰이는 toast 메시지 설정
SceneDelegate, AppDelegate에서 초기 진입점 및 상태 감지를 분기
TokenManager, TokenRefresher 리팩토링
토큰 갱신 시 재요청 흐름 정비.
Alamofire RequestInterceptor및Moya Plugin이AuthService와 연동되도록 개선.refresh 실패 시 로그아웃 처리 자동화.
💬리뷰 요구사항(선택)
AuthService도입과 함께 refresh token 만료 시 자동 로그아웃 처리가 적용되었습니다.이는 refresh token이 만료된 상황에서 별도 흐름으로 분리해 처리하기에는 구조상 복잡도가 높아,
우선은 자동 로그아웃 방식으로 구현해봤습니다!