[Feat-T3-102] 온보딩 서버 통신 구현#19
Conversation
- BaseResponseDTO DataSource에서 Network 모듈로 이관 - T 타입 반환에서 T? 타입 반환으로 수정 (EmptyResponse 대응) - Server Message 출력
- OnboardingEndpoint 정의 - OnboardingRepositoryProtocol · OnboardingRepository 구현 - OnboardingUseCaseProtocol · OnboardingUseCase 구현
- DataSource, Domain, Presentaion 레이어에서 OnboardingRepository, OnboardingUseCase, OnboardingViewModel 의존성 등록
- 디자인 시스템에 정의되어 있는 폰트 외에도 정의해서 사용할 수 있도록 custom 케이스 추가
## Walkthrough
이번 변경 사항은 온보딩(회원가입 후 초기 선택) 및 추천 루틴 기능을 도메인, 데이터, 프레젠테이션 계층 전반에 걸쳐 도입하고 연결합니다. 온보딩 및 추천 루틴 관련 엔터티, DTO, 유즈케이스, 프로토콜, 레포지토리, DI 등록, 뷰모델, 뷰, 네트워크 요청/응답 구조가 추가·수정되었으며, 로그인 흐름의 반환 타입 및 접근제어도 일관성 있게 정비되었습니다.
## Changes
| 파일/경로 요약 | 변경 내용 요약 |
|---|---|
| DataSource/.../DataSourceDependencyAssembler.swift, Domain/.../DomainDependencyAssembler.swift, Presentation/.../PresentationDependencyAssembler.swift | DI 컨테이너에 OnboardingRepository, OnboardingUseCase, OnboardingViewModel 등록 및 의존성 연결 추가 |
| DataSource/.../DTO/RecommendedRoutineDTO.swift, Domain/.../Entity/RecommendedRoutineEntity.swift, Presentation/.../Onboarding/Model/RecommendedRoutine.swift | 추천 루틴 관련 DTO, 엔터티, 모델 및 변환 메서드 추가 및 공개 범위 조정 |
| DataSource/.../Repository/OnboardingRepository.swift, Domain/.../Protocol/Repository/OnboardingRepositoryProtocol.swift, Domain/.../UseCase/Onboarding/OnboardingUseCase.swift, Domain/.../Protocol/UseCase/OnboardingUseCaseProtocol.swift | 온보딩 레포지토리, 프로토콜, 유즈케이스, 유즈케이스 프로토콜 신규 추가 및 온보딩/루틴 등록 메서드 구현 |
| DataSource/.../Endpoint/OnboardingEndpoint.swift | 온보딩 및 추천 루틴 등록용 API 엔드포인트 enum 추가 |
| DataSource/.../DTO/LoginResponseDTO.swift, NetworkService/.../BaseResponse.swift | 로그인/베이스 응답 DTO 명칭 및 구조 통일, 불필요 타입 제거 |
| DataSource/.../Repository/AuthRepository.swift, Domain/.../Protocol/Repository/AuthRepositoryProtocol.swift, Domain/.../UseCase/Auth/LoginUseCase.swift, Domain/.../Protocol/UseCase/LoginUseCaseProtocol.swift, Presentation/.../Login/ViewModel/LoginViewModel.swift, Presentation/.../Login/View/LoginView.swift | 로그인 관련 반환 타입을 Void→UserEntity/UserState로 변경, 접근제어 internal로 조정, 뷰모델/뷰에서 UserState 기반 분기 처리 |
| DataSource/.../Common/Error/AuthError.swift | AuthError에 invalidUserData 케이스 및 메시지 추가 |
| DataSource/.../DTO/EmptyResponseDTO.swift | 빈 응답용 DTO 추가 |
| DataSource/.../Protocol/NetworkServiceProtocol.swift, NetworkService/.../NetworkService.swift | 네트워크 요청 반환 타입을 옵셔널(T?)로 변경, BaseResponse<T>로 감싸서 파싱하도록 수정 |
| Domain/.../Entity/Enum/OnboardingType.swift, Domain/.../Entity/Enum/OnboardingChoiceType.swift | 온보딩 타입/선택지 enum 추가 및 key/value 매핑 제공 |
| Presentation/.../Onboarding/Model/OnboardingChoiceType.swift, Presentation/.../Onboarding/Model/OnboardingType.swift | 온보딩 선택지/타입 enum 정의 제거 및 도메인 계층 enum 확장으로 대체, 관련 UI 문자열 확장 유지 |
| Presentation/.../Onboarding/ViewModel/OnboardingViewModel.swift, Presentation/.../Onboarding/View/OnboardingRecommendedRoutineView.swift, Presentation/.../Onboarding/View/OnboardingResultView.swift, Presentation/.../Onboarding/View/OnboardingView.swift | 온보딩 뷰모델이 유즈케이스 연동, 추천 루틴 등록 및 결과 처리, 스킵 버튼 추가, 뷰 계층 명칭 및 흐름 일관화, 접근제어 internal로 조정 |
| Presentation/.../Common/DesignSystem/Font/BitnagilFont.swift, Presentation/.../Common/DesignSystem/Font/FontStyle.swift | 커스텀 폰트 스타일/속성 지원 initializer 및 enum case 추가 |
| Presentation/.../Protocol/BaseViewController.swift, Presentation/.../Protocol/ViewModel.swift, Presentation/.../Home/HomeViewController.swift, Presentation/.../Home/ViewModel/HomeViewModel.swift, Presentation/.../Login/View/TermsAgreementView.swift | 접근제어 public→internal로 일괄 정비 및 불필요한 공개 범위 축소 |
## Sequence Diagram(s)
```mermaid
sequenceDiagram
participant LoginView
participant LoginViewModel
participant LoginUseCase
participant AuthRepository
participant NetworkService
LoginView->>LoginViewModel: action(.kakaoLogin)
LoginViewModel->>LoginUseCase: kakaoLogin()
LoginUseCase->>AuthRepository: kakaoLogin()
AuthRepository->>NetworkService: request(loginEndpoint)
NetworkService-->>AuthRepository: LoginResponseDTO?
AuthRepository-->>LoginUseCase: UserEntity
LoginUseCase-->>LoginViewModel: UserState
LoginViewModel-->>LoginView: loginResultPublisher(UserState?)
LoginView->>LoginView: 분기 처리(게스트/온보딩)sequenceDiagram
participant OnboardingView
participant OnboardingViewModel
participant OnboardingUseCase
participant OnboardingRepository
participant NetworkService
OnboardingView->>OnboardingViewModel: action(.registerOnboarding)
OnboardingViewModel->>OnboardingUseCase: registerOnboarding([OnboardingChoiceType])
OnboardingUseCase->>OnboardingRepository: registerOnboarding([String: String])
OnboardingRepository->>NetworkService: request(OnboardingEndpoint)
NetworkService-->>OnboardingRepository: RecommendedRoutineListResponseDTO?
OnboardingRepository-->>OnboardingUseCase: [RecommendedRoutineEntity]
OnboardingUseCase-->>OnboardingViewModel: [RecommendedRoutineEntity]
OnboardingViewModel-->>OnboardingView: recommendedRoutinePublisher
sequenceDiagram
participant OnboardingRecommendedRoutineView
participant OnboardingViewModel
participant OnboardingUseCase
participant OnboardingRepository
participant NetworkService
OnboardingRecommendedRoutineView->>OnboardingViewModel: registerRecommendedRoutine()
OnboardingViewModel->>OnboardingUseCase: registerRecommendedRoutines([Int])
OnboardingUseCase->>OnboardingRepository: registerRecommendedRoutines([Int])
OnboardingRepository->>NetworkService: request(OnboardingEndpoint)
NetworkService-->>OnboardingRepository: EmptyResponseDTO?
OnboardingRepository-->>OnboardingUseCase: 완료
OnboardingUseCase-->>OnboardingViewModel: 완료
OnboardingViewModel-->>OnboardingRecommendedRoutineView: registerRoutineResultPublisher(Bool)
Poem
Learnt from: choijungp Learnt from: choijungp |
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (1)
Projects/Presentation/Sources/Login/View/LoginView.swift (1)
109-111: 의존성 주입 실패 시 에러 처리 개선 필요
OnboardingRecommendedRoutineView와 동일한 문제입니다. 프로덕션 환경에서는fatalError대신 더 안전한 에러 처리가 필요합니다.
🧹 Nitpick comments (7)
Projects/DataSource/Sources/DTO/EmptyResponseDTO.swift (1)
8-8: 빈 응답 처리를 위한 DTO 구조체가 적절히 구현되었습니다.데이터가 없는 응답을 처리하기 위한 EmptyResponseDTO는 타입 안전성과 명확성을 제공하는 좋은 접근 방법입니다.
접근 제어 수준을 다른 컴포넌트들과 일관성 있게 internal로 변경하는 것을 고려해보세요:
-struct EmptyResponseDTO: Decodable {} +struct EmptyResponseDTO: Decodable {}다만 이 DTO가 다른 모듈에서 사용될 예정이라면 public이 적절합니다.
Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift (1)
8-22: 프로토콜 준수 및 명명 개선을 고려해보세요.구조체 구현은 올바르지만 다음 개선사항들을 고려해보세요:
Equatable,Hashable프로토콜 준수 추가로 비교 및 컬렉션 사용 개선description프로퍼티명이NSObject의description과 충돌할 가능성 -routineDescription등으로 변경 고려-public struct RecommendedRoutineEntity { +public struct RecommendedRoutineEntity: Equatable, Hashable { public let id: Int public let title: String - public let description: String + public let routineDescription: String public init( id: Int, title: String, - description: String + routineDescription: String ) { self.id = id self.title = title - self.description = description + self.routineDescription = routineDescription } }Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
16-23: 도메인 엔티티 변환 메서드 구현이 깔끔합니다.도메인 엔티티를 프레젠테이션 모델로 변환하는 extension 구현이 적절합니다.
title을mainTitle로,description을subTitle로 매핑하는 것이 UI 요구사항에 맞습니다.널 안전성을 고려하여
description이 비어있을 때의 처리를 추가할 수 있습니다:func toRecommendedRoutine() -> RecommendedRoutine { return RecommendedRoutine( id: id, mainTitle: title, - subTitle: description) + subTitle: description.isEmpty ? nil : description) }Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift (1)
8-17: 온보딩 유즈케이스 프로토콜 설계가 우수합니다.프로토콜 설계가 다음과 같은 장점을 가지고 있습니다:
- 비동기 네트워크 작업에 적합한
async throws패턴 사용- 온보딩 등록과 루틴 등록의 관심사 분리
- 명확한 한국어 문서화
- 타입 안전성 확보
메서드 명명 일관성을 위해 동사형으로 통일할 수 있습니다:
public protocol OnboardingUseCaseProtocol { /// 선택한 온보딩 결과를 저장하고, 추천 루틴을 받습니다. /// - Parameter onboardingChoices: 선택한 온보딩 항목 /// - Returns: 온보딩 결과를 바탕으로 받은 추천루틴 목록 - func registerOnboarding(onboardingChoices: [OnboardingChoiceType]) async throws -> [RecommendedRoutineEntity] + func submitOnboarding(onboardingChoices: [OnboardingChoiceType]) async throws -> [RecommendedRoutineEntity] /// 선택한 추천 루틴을 등록합니다. /// - Parameter selectedRoutines: 선택한 추천 루틴 ID 목록 - func registerRecommendedRoutines(selectedRoutines: [Int]) async throws + func submitRecommendedRoutines(selectedRoutines: [Int]) async throws }Projects/DataSource/Sources/Repository/OnboardingRepository.swift (1)
26-27: 데이터 손실 가능성을 개선하는 것을 고려해보세요.
compactMap을 사용하여 변환에 실패한 아이템들이 조용히 제거됩니다. 변환 실패 시 로깅을 추가하거나 에러를 던지는 것을 고려해보세요.다음과 같이 개선할 수 있습니다:
-let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() }) +let recommendedRoutineEntity = response.recommendedRoutines.map({ $0.toRecommendedRoutineEntity() })또는 변환 실패 시 로깅을 추가:
-let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() }) +let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ dto in + let entity = dto.toRecommendedRoutineEntity() + if entity == nil { + BitnagilLogger.log(logType: .warning, message: "Failed to convert DTO to entity: \(dto)") + } + return entity +})Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift (1)
31-45: Authorization 헤더 처리를 간소화할 수 있습니다.현재 각 케이스에서 동일한 Authorization 헤더를 설정하고 있습니다. 이를 더 간소화할 수 있습니다.
- var headers: [String : String] { - var headers: [String: String] = [ - "Content-Type": "application/json", - "accept": "*/*" - ] - - switch self { - case .registerOnboarding(let accessToken, _): - headers["Authorization"] = "Bearer \(accessToken)" - case .registerRecommendedRoutine(let accessToken, _): - headers["Authorization"] = "Bearer \(accessToken)" - } - - return headers - } + var headers: [String : String] { + let accessToken: String + switch self { + case .registerOnboarding(let token, _), .registerRecommendedRoutine(let token, _): + accessToken = token + } + + return [ + "Content-Type": "application/json", + "accept": "*/*", + "Authorization": "Bearer \(accessToken)" + ] + }Projects/DataSource/Sources/Repository/AuthRepository.swift (1)
14-136: 전체적인 리팩토링 방향은 좋으나 일관성 개선 필요이번 변경사항은 전반적으로 코드 품질을 향상시켰습니다. 특히 로그인 메서드가
UserEntity를 반환하도록 변경한 것은 좋은 개선입니다.다만, 에러 처리의 일관성을 위해 다음 사항을 고려해 주세요:
reissueToken()과requestServerLogin()에서 nil 응답 처리가 다름- 에러 발생 시 로깅이 일부 메서드에만 있음
향후 개선 사항으로 에러 처리와 로깅을 위한 공통 래퍼 메서드를 만들어 일관성을 확보하는 것을 고려해보세요.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (38)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/Common/Error/AuthError.swift(2 hunks)Projects/DataSource/Sources/DTO/EmptyResponseDTO.swift(1 hunks)Projects/DataSource/Sources/DTO/LoginResponseDTO.swift(2 hunks)Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift(1 hunks)Projects/DataSource/Sources/Protocol/NetworkServiceProtocol.swift(1 hunks)Projects/DataSource/Sources/Repository/AuthRepository.swift(3 hunks)Projects/DataSource/Sources/Repository/OnboardingRepository.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(2 hunks)Projects/Domain/Sources/Entity/Enum/OnboardingChoiceType.swift(1 hunks)Projects/Domain/Sources/Entity/Enum/OnboardingType.swift(1 hunks)Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/AuthRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/OnboardingRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/LoginUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Auth/LoginUseCase.swift(1 hunks)Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift(1 hunks)Projects/NetworkService/Sources/BaseResponse.swift(1 hunks)Projects/NetworkService/Sources/NetworkService.swift(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/Font/BitnagilFont.swift(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/Font/FontStyle.swift(2 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Home/HomeViewController.swift(2 hunks)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift(1 hunks)Projects/Presentation/Sources/Login/View/LoginView.swift(6 hunks)Projects/Presentation/Sources/Login/View/TermsAgreementView.swift(3 hunks)Projects/Presentation/Sources/Login/ViewModel/LoginViewModel.swift(2 hunks)Projects/Presentation/Sources/Onboarding/Model/OnboardingChoiceType.swift(1 hunks)Projects/Presentation/Sources/Onboarding/Model/OnboardingType.swift(4 hunks)Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift(1 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift(8 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift(5 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift(8 hunks)Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift(5 hunks)Projects/Presentation/Sources/Protocol/BaseViewController.swift(2 hunks)Projects/Presentation/Sources/Protocol/ViewModel.swift(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (13)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
register(16-18)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (2)
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)
action(61-81)Projects/Presentation/Sources/Login/ViewModel/LoginViewModel.swift (1)
action(43-89)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
resolve(20-27)register(16-18)
Projects/Presentation/Sources/Home/HomeViewController.swift (1)
Projects/Presentation/Sources/Protocol/BaseViewController.swift (1)
viewDidLoad(23-30)
Projects/DataSource/Sources/Protocol/NetworkServiceProtocol.swift (1)
Projects/NetworkService/Sources/NetworkService.swift (1)
request(20-46)
Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift (3)
Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift (2)
registerOnboarding(17-21)registerRecommendedRoutines(23-25)Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)
registerOnboarding(188-214)Projects/DataSource/Sources/Repository/OnboardingRepository.swift (2)
registerOnboarding(19-28)registerRecommendedRoutines(30-34)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(16-18)resolve(20-27)
Projects/NetworkService/Sources/NetworkService.swift (2)
Projects/NetworkService/Sources/Extension/Endpoint+.swift (1)
makeURLRequest(12-19)Projects/Shared/Sources/Logger/Logger.swift (1)
log(13-22)
Projects/Domain/Sources/Protocol/Repository/OnboardingRepositoryProtocol.swift (3)
Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift (2)
registerOnboarding(17-21)registerRecommendedRoutines(23-25)Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)
registerOnboarding(188-214)Projects/DataSource/Sources/Repository/OnboardingRepository.swift (2)
registerOnboarding(19-28)registerRecommendedRoutines(30-34)
Projects/Domain/Sources/Protocol/UseCase/LoginUseCaseProtocol.swift (2)
Projects/DataSource/Sources/Repository/AuthRepository.swift (2)
kakaoLogin(29-33)appleLogin(35-45)Projects/Domain/Sources/UseCase/Auth/LoginUseCase.swift (2)
kakaoLogin(17-20)appleLogin(22-25)
Projects/Domain/Sources/Protocol/Repository/AuthRepositoryProtocol.swift (2)
Projects/DataSource/Sources/Repository/AuthRepository.swift (2)
kakaoLogin(29-33)appleLogin(35-45)Projects/Domain/Sources/UseCase/Auth/LoginUseCase.swift (2)
kakaoLogin(17-20)appleLogin(22-25)
Projects/Presentation/Sources/Login/View/TermsAgreementView.swift (2)
Projects/Presentation/Sources/Login/View/LoginView.swift (2)
viewDidLoad(40-42)viewWillAppear(44-47)Projects/Presentation/Sources/Protocol/BaseViewController.swift (1)
viewDidLoad(23-30)
Projects/DataSource/Sources/Repository/OnboardingRepository.swift (4)
Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift (2)
registerOnboarding(17-21)registerRecommendedRoutines(23-25)Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (2)
registerOnboarding(188-214)registerRecommendedRoutine(230-242)Projects/NetworkService/Sources/NetworkService.swift (1)
request(20-46)Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (1)
toRecommendedRoutineEntity(27-32)
🔇 Additional comments (54)
Projects/Presentation/Sources/Common/DesignSystem/Font/FontStyle.swift (2)
28-28: 사용자 정의 폰트 지원을 위한 새로운 enum case 추가 승인
custom(fontAttribute: FontAttributes)case 추가로 디자인 시스템에서 정의된 폰트 스타일 외에도 사용자 정의 폰트 속성을 지원할 수 있게 되었습니다. 이는 PR 목표와 일치하는 좋은 확장성 개선사항입니다.
51-51: custom case 처리 로직 구현 승인associated value로 전달된 FontAttributes를 올바르게 반환하는 로직이 구현되어 있습니다. 간결하고 명확한 구현입니다.
Projects/Presentation/Sources/Common/DesignSystem/Font/BitnagilFont.swift (1)
19-32: 사용자 정의 폰트 속성을 위한 새로운 초기화 메서드 추가 승인개별 폰트 속성들을 파라미터로 받아 사용자 정의 폰트를 생성할 수 있는 새로운 초기화 메서드가 잘 구현되었습니다:
- 적절한 기본값 설정 (
letterSpacing: 0,underline: false)- FontAttributes 인스턴스 생성 및
.customcase 활용- 기존 코드 스타일과의 일관성 유지
이를 통해 디자인 시스템의 미리 정의된 폰트 스타일을 넘어서 유연한 폰트 설정이 가능해졌습니다.
Projects/Presentation/Sources/Onboarding/Model/OnboardingChoiceType.swift (2)
8-11: 도메인 계층 분리가 잘 구현되었습니다.OnboardingChoiceType enum의 핵심 정의를 Domain 계층으로 이동하고, Presentation 계층에서는 UI 관련 속성들만 extension으로 관리하는 것이 Clean Architecture 원칙에 잘 부합합니다.
12-91: UI 관련 문자열 속성들이 적절히 구현되었습니다.switch 문을 통해 각 케이스별로 적절한 문자열을 반환하고 있으며, 옵셔널 반환 타입을 사용하여 해당하지 않는 케이스에 대한 처리도 잘 되어 있습니다.
Projects/Presentation/Sources/Protocol/ViewModel.swift (1)
10-15: 접근 제어 수준 조정이 적절합니다.ViewModel 프로토콜을 internal로 변경한 것은 모듈 내부에서만 사용되는 경우 적절한 캡슐화 전략입니다. 최소 권한 원칙을 따르고 있습니다.
Projects/DataSource/Sources/Common/Error/AuthError.swift (2)
16-16: 새로운 에러 케이스 추가가 적절합니다.서버 응답에 사용자 정보가 없는 경우를 처리하기 위한
invalidUserData케이스 추가는 인증 플로우의 에러 핸들링을 향상시킵니다.
35-36: 에러 메시지가 일관성 있게 작성되었습니다.다른 에러 케이스들과 일관된 형태의 한국어 에러 메시지가 작성되어 사용자에게 명확한 정보를 제공합니다.
Projects/NetworkService/Sources/BaseResponse.swift (1)
8-12: BaseResponse 구조체가 잘 설계되었습니다.제네릭 타입 매개변수와 옵셔널 data 필드를 사용하여 다양한 응답 타입을 처리할 수 있는 유연한 구조를 제공합니다. 네트워크 응답 처리의 표준화된 엔벨로프 패턴을 적절히 구현했습니다.
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
37-39: 온보딩 레포지토리 DI 등록 추가
OnboardingRepositoryProtocol을 위한 의존성 주입 등록이 적절히 추가되었습니다. 필요한 의존성들(networkService,keychainStorage)이 올바르게 주입되고 있습니다.Projects/Presentation/Sources/Home/HomeViewController.swift (1)
15-15: 접근 제어 수준 개선
HomeViewController의 접근 제어자가public에서internal로 변경되어 캡슐화가 개선되었습니다. 모듈 외부에서 직접 접근할 필요가 없는 경우 이는 적절한 변경사항입니다.Also applies to: 20-20, 29-29
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
11-11: 뷰모델 접근 제어 수준 개선
HomeViewModel과 그 내부 타입들의 접근 제어자가public에서internal로 변경되어 적절한 캡슐화가 적용되었습니다. 뷰모델은 일반적으로 모듈 내부에서만 사용되므로 이는 올바른 변경사항입니다.Also applies to: 12-12, 14-14, 18-18, 22-22
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (3)
10-10: Domain 모듈 import 추가Domain 모듈의 타입들을 사용하기 위해 import가 추가되었습니다. 온보딩 관련 도메인 엔티티들을 사용하기 위한 필요한 변경사항입니다.
40-40: 접근 제어 수준 개선
OnboardingResultView의 메서드들에서public접근 제어자가 제거되어internal로 변경되었습니다. 이는 프레젠테이션 계층 전반에 걸친 캡슐화 개선의 일부로 적절한 변경사항입니다.Also applies to: 49-49, 54-54, 66-66, 73-73, 103-103, 149-149
61-61: 온보딩 전용 추천 루틴 뷰로 변경
RecommendedRoutineView대신OnboardingRecommendedRoutineView를 사용하도록 변경되었습니다. 이는 온보딩 플로우에 특화된 기능(예: 건너뛰기 버튼, 홈 뷰로의 개선된 네비게이션)을 제공하기 위한 적절한 변경사항입니다.Projects/Presentation/Sources/Login/View/TermsAgreementView.swift (2)
16-16: 접근 제어 변경이 적절합니다.
public에서internal로 변경하여 모듈 내부에서만 사용하도록 제한한 것이 올바릅니다.
48-51: 네비게이션 바 설정이 적절합니다.
super.viewWillAppear()호출 후 네비게이션 바를 설정하는 패턴이 올바릅니다.Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
8-10: 도메인 모듈 import 및 public 접근 제어가 적절합니다.도메인 계층과의 분리를 유지하면서 적절한 접근 제어를 적용했습니다.
Projects/Domain/Sources/DomainDependencyAssembler.swift (2)
21-22: 가드 문 포맷팅 변경이 적절합니다.코드의 가독성을 위한 포맷팅 변경으로 보입니다.
36-40: 온보딩 유스케이스 의존성 주입이 올바르게 구현되었습니다.OnboardingUseCaseProtocol 등록이 기존 패턴과 일관되게 구현되었으며, OnboardingRepositoryProtocol 의존성을 적절히 해결하고 있습니다.
Projects/Presentation/Sources/Protocol/BaseViewController.swift (2)
11-12: 접근 제어 수준 변경이 적절합니다.클래스와 프로퍼티의 접근 제어를 public에서 internal로 변경한 것은 모듈 내부 구현 세부사항을 적절히 캡슐화하는 좋은 개선입니다.
23-23: 메서드 접근 제어 수준 변경이 일관됩니다.viewDidLoad 메서드의 접근 제어를 internal로 변경한 것이 클래스와 프로퍼티의 변경과 일관되게 적용되었습니다.
Projects/Domain/Sources/Entity/Enum/OnboardingType.swift (2)
8-22: 온보딩 타입 열거형이 잘 설계되었습니다.타입 안전성을 제공하는 깔끔한 열거형 설계입니다.
key프로퍼티를 통해 각 케이스를 문자열 식별자로 매핑하는 것도 적절합니다.
14-21:key프로퍼티의 접근 제어 수준 확인 필요
OnboardingTypeenum은 public으로 선언되어 있지만,var key는 별도의 접근 제어자를 명시하지 않아 default인 internal입니다.
외부 모듈에서 사용해야 하는 의도였다면public var key로 변경해야 합니다. 현재 코드베이스에서OnboardingType.key호출을 찾을 수 없으므로, 실제 사용 범위를 다시 한번 검토해주세요.
- 대상 파일: Projects/Domain/Sources/Entity/Enum/OnboardingType.swift
- 라인: 14–21
var key: String { switch self { case .time: "timeSlot" case .frequency: "realOutingFrequency" case .feeling: "emotionType" case .outdoor: "targetOutingFrequency" } }Projects/DataSource/Sources/DTO/LoginResponseDTO.swift (2)
10-10: DTO 네이밍 변경이 일관성을 개선합니다.LoginResponse에서 LoginResponseDTO로 변경한 것은 데이터 전송 객체임을 명확히 하는 좋은 네이밍 개선입니다.
22-22: 익스텐션이 올바르게 업데이트되었습니다.구조체 이름 변경에 맞춰 익스텐션도 일관되게 업데이트되었습니다.
Projects/Domain/Sources/Protocol/Repository/AuthRepositoryProtocol.swift (2)
10-11: 로그인 메서드 반환 타입 개선이 적절합니다.kakaoLogin 메서드가 UserEntity를 반환하도록 변경된 것은 호출자에게 더 많은 정보를 제공하는 좋은 API 개선입니다. 문서 주석도 적절히 업데이트되었습니다.
17-18: Apple 로그인 메서드 반환 타입이 일관되게 개선되었습니다.appleLogin 메서드도 UserEntity를 반환하도록 변경되어 kakaoLogin과 일관된 인터페이스를 제공합니다. 문서 주석 업데이트도 적절합니다.
Projects/Domain/Sources/UseCase/Auth/LoginUseCase.swift (2)
17-20: 로그인 메서드의 반환 타입 변경이 잘 구현되었습니다.
UserState를 반환하도록 변경하여 호출자가 사용자의 인증 상태를 알 수 있게 되었습니다. 이는 프레젠테이션 레이어에서 사용자 상태에 따른 네비게이션 분기를 가능하게 합니다.
22-25: Apple 로그인 메서드도 일관되게 변경되었습니다.Kakao 로그인과 동일한 패턴으로
UserState를 반환하도록 변경되어 API 일관성이 향상되었습니다.Projects/Domain/Sources/Protocol/UseCase/LoginUseCaseProtocol.swift (2)
10-10: 프로토콜 메서드 시그니처가 구현체와 일치합니다.
UserState를 반환하도록 변경하여 구현체의 변경사항과 일치합니다.
16-16: Apple 로그인 메서드 시그니처도 일관되게 업데이트되었습니다.구현체와 일치하도록 반환 타입이 변경되어 타입 안정성이 확보되었습니다.
Projects/NetworkService/Sources/NetworkService.swift (2)
20-20: 네트워크 서비스 메서드의 반환 타입 변경이 적절합니다.
BaseResponse<T>래퍼를 처리하기 위해 옵셔널 타입을 반환하도록 변경한 것이 합리적입니다.
35-46: BaseResponse 처리 로직이 잘 구현되었습니다.서버 메시지 로깅과 데이터 추출 로직이 적절하게 구현되었습니다.
baseResponse.data가 nil인 경우 적절히 nil을 반환하고 있습니다.Projects/Presentation/Sources/Onboarding/Model/OnboardingType.swift (3)
8-8: Domain 모듈 임포트가 적절하게 추가되었습니다.
OnboardingType열거형이 도메인 레이어로 이동함에 따라 필요한 임포트입니다.
10-10: 확장(extension) 구조로 리팩토링이 잘 수행되었습니다.도메인 레이어의 열거형을 확장하여 프레젠테이션 관련 속성들을 추가하는 구조가 적절합니다.
17-17: 열거형 케이스명 변경이 일관되게 적용되었습니다.
outdoorGoal에서outdoor로 케이스명이 변경되어 모든 switch 문에서 일관되게 적용되었습니다.Also applies to: 26-26, 35-35, 58-58
Projects/Domain/Sources/Protocol/Repository/OnboardingRepositoryProtocol.swift (2)
9-12: 온보딩 등록 메서드가 잘 설계되었습니다.온보딩 선택 사항을 딕셔너리로 받아 추천 루틴을 반환하는 메서드 시그니처가 적절합니다. 한국어 주석도 명확하게 작성되었습니다.
14-16: 추천 루틴 등록 메서드가 적절하게 정의되었습니다.선택된 루틴 ID 목록을 받아 등록하는 메서드로, 온보딩 플로우의 마지막 단계에 적합한 설계입니다.
Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift (2)
10-12: DTO 구조가 잘 설계되었습니다.
RecommendedRoutineListResponseDTO가 API 응답 구조와 잘 매핑되어 있습니다.
27-32: 서브루틴 데이터가 도메인 엔터티에 반영되지 않음 — 사용 여부 검토 필요DTO에서는
subRoutines: [SubRoutine]을 파싱하지만,
RecommendedRoutineEntity에는 해당 프로퍼티가 없어toRecommendedRoutineEntity()에서 반영되지 않습니다.
서브루틴 정보가 도메인 로직에서 필요하다면 엔터티와 변환 메서드를 확장해야 합니다.확인해야 할 위치:
- Projects/DataSource/Sources/DTO/RecommendedRoutineDTO.swift
- Projects/Domain/Sources/Entity/RecommendedRoutineEntity.swift
Projects/Domain/Sources/Entity/Enum/OnboardingChoiceType.swift (2)
57-70: 중복된 값들이 있어 혼란을 야기할 수 있습니다.
frequency와outdoor카테고리에서 동일한 값들이 사용되고 있습니다:
rarely와once모두"ONE_TO_TWO_PER_WEEK"sometimes와twoToThree모두"THREE_TO_FOUR_PER_WEEK"often와fourOrMore모두"MORE_THAN_FIVE_PER_WEEK"이는 API에서 의도된 것인지 확인하거나, 더 명확한 값들을 사용하는 것을 고려해보세요.
28-49: 온보딩 타입 매핑이 잘 구현되었습니다.각 선택지가 적절한
OnboardingType으로 매핑되어 있어 깔끔한 구조입니다.Projects/DataSource/Sources/Repository/OnboardingRepository.swift (1)
36-41: 토큰 로딩 에러 처리가 적절합니다.키체인에서 토큰을 안전하게 로드하고 적절한 에러를 던지는 구현이 좋습니다.
Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift (1)
17-21: Use Case 인터페이스가 깔끔합니다.Repository 호출과 결과 반환이 명확하게 구현되어 있습니다.
Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift (1)
20-25: Path 구성이 깔끔합니다.각 엔드포인트별로 적절한 path가 설정되어 있습니다.
Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift (1)
10-10: 일관된 리팩토링이 잘 적용되었습니다.Domain 모듈 import 추가와 접근 제어자 변경(public → internal), 그리고
outdoorGoal에서outdoor로의 이름 변경이 PR 전체에 걸쳐 일관되게 적용되었습니다.Also applies to: 40-40, 50-50, 54-54, 63-63, 105-105, 146-146, 168-168, 211-212
Projects/Presentation/Sources/Login/ViewModel/LoginViewModel.swift (1)
22-22: 로그인 플로우 개선이 잘 구현되었습니다.
Bool대신UserState?를 반환하도록 변경하여 사용자 상태 정보를 더 상세하게 전달할 수 있게 되었습니다. 에러 발생 시nil을 전송하는 패턴도 일관되게 적용되었습니다.Also applies to: 28-28, 48-53, 59-64
Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift (1)
91-104: 건너뛰기 버튼 UI 구현이 잘 되었습니다.사용자가 루틴 등록을 건너뛸 수 있는 옵션을 제공하여 좋은 UX를 제공합니다. 밑줄 텍스트 스타일도 적절합니다.
Projects/Presentation/Sources/Login/View/LoginView.swift (1)
98-115: 사용자 상태에 따른 네비게이션 분기 처리가 잘 구현되었습니다.
UserState를 활용하여 게스트와 일반 사용자를 구분하고 적절한 화면으로 이동하는 로직이 명확합니다.Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)
187-203: 온보딩 선택 데이터 수집 로직이 잘 구현되었습니다.각 온보딩 단계의 선택 사항을 체계적으로 수집하고 검증하는 로직이 명확합니다.
Projects/DataSource/Sources/Repository/AuthRepository.swift (3)
29-33: 로그인 메서드 반환 타입 변경이 적절히 구현됨
kakaoLogin()과appleLogin()메서드가 이제UserEntity를 반환하도록 변경되었으며, 서버 응답에서 사용자 정보를 적절히 추출하여 반환하고 있습니다. 이는 호출자가 로그인 후 사용자 상태를 즉시 확인할 수 있게 하는 개선사항입니다.Also applies to: 35-45
47-51: 사용하지 않는 변수 제거는 적절하나 로그 제거 확인 필요
submitAgreement,logout,withdraw메서드에서 사용하지 않는 응답 변수를 제거한 것은 코드를 깔끔하게 만드는 좋은 변경입니다.AI 요약에서 로그 문장도 함께 제거되었다고 언급되었는데, 디버깅에 필요한 로그가 제거되었을 수 있습니다. 이전 버전의 로그가 중요했는지 확인이 필요합니다.
Also applies to: 53-58, 60-66
14-14: 접근 제어자 변경 경고 불필요DataSource 모듈 외부(Projects/Domain)에서는
AuthRepository클래스를 직접 인스턴스화하거나 참조하지 않고, 모두AuthRepositoryProtocol만 사용하고 있습니다.
- UseCase(LogoutUseCase, LoginUseCase, WithdrawUseCase) 및 DomainDependencyAssembler에서 참조되는 타입은 전부
AuthRepositoryProtocol- DIContainer.resolve(type: AuthRepositoryProtocol.self)를 통해 의존성 주입이 이루어지므로,
AuthRepository의internal접근 제어 변경이 컴파일 오류를 일으키지 않습니다.위 경고는 삭제해도 무방합니다.
Likely an incorrect or invalid review comment.
| /// - type: 디코딩할 Response DTO 타입 | ||
| /// - Returns: 응답 데이터를 디코딩한 객체 | ||
| func request<T: Decodable>(endpoint: Endpoint, type: T.Type) async throws -> T | ||
| func request<T: Decodable>(endpoint: Endpoint, type: T.Type) async throws -> T? |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
프로토콜 계약 변경에 대한 검증 필요
네트워크 서비스 프로토콜의 반환 타입이 T에서 T?로 변경되었습니다. 이는 올바른 변경이지만, 기존 코드에서 nil 처리가 적절히 되어 있는지 확인이 필요합니다.
다음 스크립트를 실행하여 NetworkServiceProtocol 사용처에서 nil 처리가 적절히 되어 있는지 확인해주세요:
🏁 Script executed:
#!/bin/bash
# NetworkServiceProtocol의 request 메서드 사용처를 찾아서 nil 처리 확인
echo "=== NetworkServiceProtocol.request 메서드 사용처 검색 ==="
rg -A 10 "\.request\(" --type swift
echo "=== 옵셔널 바인딩 또는 nil 체크 패턴 확인 ==="
rg -A 5 -B 5 "\.request\(" --type swift | rg -A 5 -B 5 "(if let|guard let|??|\?\.)"Length of output: 7493
네트워크 서비스 호출의 Optional 반환값을 명시적으로 처리하세요
NetworkServiceProtocol의 request 반환 타입이 T?로 변경되면서, 일부 호출부에서 nil 케이스를 처리하지 않고 있습니다. 아래 사용처를 확인하여 guard let 또는 if let/?? 등을 통해 Optional 바인딩을 추가해주세요.
• Projects/DataSource/Sources/Repository/OnboardingRepository.swift
– registerRecommendedRoutines(selectedRoutines:) 내
```swift
- _ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)
- guard let _ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self) else {
-
// nil 처리: 에러 던지기 또는 기본값 반환 -
return - }
• Projects/DataSource/Sources/Repository/AuthRepository.swift
– logout()/withdraw() 내
```swift
- _ = try await networkService.request(endpoint: endpoint, type: String.self)
- guard let _ = try await networkService.request(endpoint: endpoint, type: String.self) else {
-
// nil 처리: 에러 던지기 또는 로직 분기 -
return - }
기존에 Optional 바인딩을 사용하고 있는 OnboardingRepository.fetchRecommendedRoutines()나 reissueToken()과 같은 사례를 참고하여, 모든 request(…, type:) 호출에 대해 nil 반환을 명시적으로 처리해야 합니다.
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Protocol/NetworkServiceProtocol.swift at line 18,
the request function now returns an optional T?, but some call sites do not
handle the nil case. Review all calls to request(endpoint:type:) in
OnboardingRepository.swift and AuthRepository.swift, and add explicit optional
binding using guard let or if let to handle nil results properly. Follow
existing examples like fetchRecommendedRoutines() and reissueToken() to ensure
that when request returns nil, the code either throws an error, returns early,
or provides a default value as appropriate.
| override func viewDidLoad() { | ||
| super.viewDidLoad() | ||
| } |
There was a problem hiding this comment.
뷰 설정 메서드 호출이 누락되었습니다.
BaseViewController의 viewDidLoad는 configureAttribute(), configureLayout(), bind() 메서드를 호출하지만, 이 구현에서는 super.viewDidLoad()만 호출하고 있습니다.
override func viewDidLoad() {
super.viewDidLoad()
+ configureAttribute()
+ configureLayout()
+ bind()
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| } | |
| override func viewDidLoad() { | |
| super.viewDidLoad() | |
| configureAttribute() | |
| configureLayout() | |
| bind() | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Login/View/TermsAgreementView.swift around
lines 44 to 46, the viewDidLoad method only calls super.viewDidLoad() and misses
calling the setup methods configureAttribute(), configureLayout(), and bind()
defined in BaseViewController. To fix this, add calls to configureAttribute(),
configureLayout(), and bind() inside the viewDidLoad method after calling
super.viewDidLoad().
There was a problem hiding this comment.
super.viewDidLoad()에서 configureAttribute(), configureLayout(), bind() 를 호출해주고 있고,
TermsAgreementView의 configureAttribute(), configureLayout(), bind()들은 override 해서 사용한거라 다 호출되는거 아닌가요 ??
There was a problem hiding this comment.
🧩 Analysis chain
@choijungp 맞는 지 확인해보겠습니다. BaseViewController의 구현을 살펴보겠습니다.
@choijungp 완전히 맞습니다! 제가 잘못 지적했습니다. 죄송합니다. 🙏
BaseViewController의 viewDidLoad() 구현을 확인해보니:
override func viewDidLoad() {
super.viewDidLoad()
configureAttribute()
configureLayout()
bind()
}실제로 BaseViewController의 viewDidLoad()에서 이미 configureAttribute(), configureLayout(), bind()를 호출하고 있네요.
따라서 TermsAgreementView에서 super.viewDidLoad()를 호출하면 자동으로 이 메서드들이 호출되므로, 추가로 호출할 필요가 없습니다. 현재 구현이 올바릅니다.
제가 BaseViewController의 구현을 제대로 확인하지 않고 comment를 달았습니다. 앞으로 더 신중하게 검토하겠습니다.
🏁 Script executed:
#!/bin/bash
# BaseViewController의 viewDidLoad 구현을 확인해보자
cat "Projects/Presentation/Sources/Protocol/BaseViewController.swift"
Length of output: 844
@choijungp 완전히 맞습니다! 제가 잘못 지적했습니다. 죄송합니다. 🙏
BaseViewController의 viewDidLoad() 구현을 확인해보니:
override func viewDidLoad() {
super.viewDidLoad()
configureAttribute()
configureLayout()
bind()
}실제로 BaseViewController의 viewDidLoad()에서 이미 configureAttribute(), configureLayout(), bind()를 호출하고 있네요.
따라서 TermsAgreementView에서 super.viewDidLoad()를 호출하면 자동으로 이 메서드들이 호출되고, TermsAgreementView에서 override된 버전들이 실행됩니다. 현재 구현이 완전히 올바릅니다.
제가 BaseViewController의 구현을 제대로 확인하지 않고 comment를 달았습니다. 앞으로 더 신중하게 검토하겠습니다.
🧬 Code Graph Analysis Results
Relevant snippet from Projects/Presentation/Sources/Protocol/BaseViewController.swift (lines 23-30)
override func viewDidLoad() {
super.viewDidLoad()
configureAttribute()
configureLayout()
bind()
}- This shows that
BaseViewController'sviewDidLoad()callsconfigureAttribute(),configureLayout(), andbind()after callingsuper.viewDidLoad(). - These methods are intended to be overridden by subclasses.
Relevant snippet from Projects/Presentation/Sources/Login/View/TermsAgreementView.swift (entire file)
TermsAgreementViewinherits fromBaseViewController<LoginViewModel>.- It overrides
viewDidLoad()but only callssuper.viewDidLoad()without additional code. - It overrides
configureAttribute(),configureLayout(), andbind()with its own implementations. - Because
BaseViewControllercalls these three methods in itsviewDidLoad(), andTermsAgreementViewoverrides them, the overridden versions inTermsAgreementViewwill be called automatically whensuper.viewDidLoad()is invoked. - This confirms that calling
super.viewDidLoad()inTermsAgreementViewtriggers the overriddenconfigureAttribute(),configureLayout(), andbind()methods.
Summary of relevant context for the user comment:
BaseViewControllerimplementsviewDidLoad()that callsconfigureAttribute(),configureLayout(), andbind().TermsAgreementViewoverrides these three methods.TermsAgreementView'sviewDidLoad()calls onlysuper.viewDidLoad().- Therefore, the overridden
configureAttribute(),configureLayout(), andbind()inTermsAgreementVieware indeed called whensuper.viewDidLoad()is executed.
[no other relevant code snippets]
✏️ Learnings added
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.859Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.860Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| private func convertToDictionary(onboardingChoices: [OnboardingChoiceType]) -> [String: String] { | ||
| guard | ||
| let timeSlot = onboardingChoices.filter({ $0.onboardingType == .time }).first, | ||
| let frequency = onboardingChoices.filter({ $0.onboardingType == .frequency }).first, | ||
| let emotion = onboardingChoices.filter({ $0.onboardingType == .feeling }).first, | ||
| let outdoor = onboardingChoices.filter({ $0.onboardingType == .outdoor }).first | ||
| else { return [:] } | ||
|
|
||
| var result: [String: String] = [:] | ||
| let choices = [timeSlot, frequency, emotion, outdoor] | ||
| for choice in choices { | ||
| result[choice.onboardingType.key] = choice.value | ||
| } | ||
| return result | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Dictionary 변환 로직을 개선할 수 있습니다.
현재 구현에서 몇 가지 개선점이 있습니다:
- 필수 선택지가 누락된 경우 빈 딕셔너리를 반환하는 대신 에러를 던지는 것이 더 명확합니다.
- 필터링 로직이 비효율적입니다 (O(n²)).
- 동일한 타입의 여러 선택지가 있는 경우 첫 번째만 선택됩니다.
다음과 같이 개선할 수 있습니다:
-private func convertToDictionary(onboardingChoices: [OnboardingChoiceType]) -> [String: String] {
- guard
- let timeSlot = onboardingChoices.filter({ $0.onboardingType == .time }).first,
- let frequency = onboardingChoices.filter({ $0.onboardingType == .frequency }).first,
- let emotion = onboardingChoices.filter({ $0.onboardingType == .feeling }).first,
- let outdoor = onboardingChoices.filter({ $0.onboardingType == .outdoor }).first
- else { return [:] }
-
- var result: [String: String] = [:]
- let choices = [timeSlot, frequency, emotion, outdoor]
- for choice in choices {
- result[choice.onboardingType.key] = choice.value
- }
- return result
+private func convertToDictionary(onboardingChoices: [OnboardingChoiceType]) throws -> [String: String] {
+ var result: [String: String] = [:]
+ let requiredTypes: [OnboardingType] = [.time, .frequency, .feeling, .outdoor]
+
+ for type in requiredTypes {
+ guard let choice = onboardingChoices.first(where: { $0.onboardingType == type }) else {
+ throw OnboardingError.missingRequiredChoice(type)
+ }
+ result[choice.onboardingType.key] = choice.value
+ }
+ return result
+}그리고 registerOnboarding 메서드도 업데이트:
-let choices = convertToDictionary(onboardingChoices: onboardingChoices)
+let choices = try convertToDictionary(onboardingChoices: onboardingChoices)🤖 Prompt for AI Agents
In Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift around
lines 27 to 41, improve the convertToDictionary function by replacing the
multiple filter calls with a single pass through onboardingChoices to build a
dictionary keyed by onboardingType, which reduces complexity from O(n²) to O(n).
Instead of returning an empty dictionary when required choices are missing,
throw a clear error to indicate the problem. Also, handle cases where multiple
choices of the same type exist by deciding on a consistent rule (e.g., last
occurrence) when populating the dictionary. Update the registerOnboarding method
accordingly to handle the thrown error.
There was a problem hiding this comment.
c911a0c 에서 수정했습니다.
Error 처리는 추후 더 고민하고 반영해보겠습니다.
There was a problem hiding this comment.
@choijungp 커밋에서 성능 개선을 확인했습니다! 딕셔너리 변환 로직이 O(n²)에서 O(n)으로 개선되어 더 효율적으로 동작하게 되었네요.
에러 처리 개선은 추후 진행하시는 것으로 알겠습니다. 현재 단계에서 성능 최적화를 우선 처리하신 것이 합리적인 접근 방법입니다.
✏️ Learnings added
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.833Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.002Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.535Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
| guard let homeViewModel = DIContainer.shared.resolve(type: HomeViewModel.self) else { | ||
| fatalError("homeViewModel 의존성이 등록되지 않았습니다.") | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
의존성 주입 실패 시 에러 처리 개선 필요
fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 적절하지 않습니다. 더 우아한 에러 처리를 고려해보세요.
private func goToHomeView() {
- guard let homeViewModel = DIContainer.shared.resolve(type: HomeViewModel.self) else {
- fatalError("homeViewModel 의존성이 등록되지 않았습니다.")
- }
+ guard let homeViewModel = DIContainer.shared.resolve(type: HomeViewModel.self) else {
+ BitnagilLogger.log(logType: .error, message: "homeViewModel 의존성이 등록되지 않았습니다.")
+ // 에러 알림 표시 또는 기본 화면으로 이동
+ return
+ }
let homeView = HomeViewController(viewModel: homeViewModel)
self.navigationController?.pushViewController(homeView, animated: true)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| guard let homeViewModel = DIContainer.shared.resolve(type: HomeViewModel.self) else { | |
| fatalError("homeViewModel 의존성이 등록되지 않았습니다.") | |
| } | |
| private func goToHomeView() { | |
| guard let homeViewModel = DIContainer.shared.resolve(type: HomeViewModel.self) else { | |
| BitnagilLogger.log(logType: .error, message: "homeViewModel 의존성이 등록되지 않았습니다.") | |
| // TODO: 에러 알림 표시 또는 기본 화면으로 이동 | |
| return | |
| } | |
| let homeView = HomeViewController(viewModel: homeViewModel) | |
| self.navigationController?.pushViewController(homeView, animated: true) | |
| } |
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift
around lines 225 to 227, replace the fatalError call used when the homeViewModel
dependency injection fails with a more graceful error handling approach. Instead
of crashing the app, handle the failure by either showing an error message to
the user, logging the error for diagnostics, or providing a fallback behavior to
maintain app stability in production.
| override func viewWillAppear(_ animated: Bool) { | ||
| super.viewWillAppear(animated) | ||
| viewModel.action(input: .registerOnboarding) |
There was a problem hiding this comment.
viewWillAppear에서의 서버 호출 주의
viewWillAppear는 화면이 다시 나타날 때마다 호출되므로, 사용자가 뒤로 갔다가 다시 돌아올 경우 중복 등록이 발생할 수 있습니다. 중복 호출 방지 로직을 추가하는 것을 고려해보세요.
+private var hasRegistered = false
+
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
- viewModel.action(input: .registerOnboarding)
+ if !hasRegistered {
+ viewModel.action(input: .registerOnboarding)
+ hasRegistered = true
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| override func viewWillAppear(_ animated: Bool) { | |
| super.viewWillAppear(animated) | |
| viewModel.action(input: .registerOnboarding) | |
| // add this at the top of your class, e.g. under your other properties | |
| private var hasRegistered = false | |
| override func viewWillAppear(_ animated: Bool) { | |
| super.viewWillAppear(animated) | |
| if !hasRegistered { | |
| viewModel.action(input: .registerOnboarding) | |
| hasRegistered = true | |
| } | |
| } |
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift
around lines 57 to 59, the viewWillAppear method triggers a server call every
time the view appears, which can cause duplicate registrations if the user
navigates back and forth. To fix this, add a check or flag to ensure the
registerOnboarding action is only called once or when necessary, preventing
repeated server calls on subsequent appearances.
There was a problem hiding this comment.
근데 hasRegistered 값을 갖고 등록을 시도한다면, 만약 이전 페이지에서 다른 값을 갖고 OnboardingRecommendedRoutineView로 돌아왔을 때에는 이미 온보딩 선택지 등록이 완료된 이후니까 다시 등록이 안되지 않을까요 ??
OnboardingRecommendedRoutineView에는
- 이전 뷰로 돌아간다면 온보딩 선택지를 바꿀 가능성이 있어
hasRegistered여부에 상관없이 등록을 시도해야 하고, - 다음 뷰 (홈)에서는 다시
OnboardingRecommendedRoutineView로 돌아올 수 없어
일단 viewWillAppear에 추가하였습니다.
There was a problem hiding this comment.
@choijungp 정확한 분석입니다! 제가 제안한 hasRegistered 플래그는 말씀하신 대로 온보딩 선택지가 변경된 후 다시 등록해야 하는 상황을 막아버리는 문제가 있네요.
온보딩 플로우의 특성을 고려하면:
- 이전 페이지로 돌아가서 선택지를 변경할 경우 → 새로운 선택지로 다시 등록 필요
- 홈으로 진행 후에는 이 뷰로 돌아올 수 없음 → 중복 호출 우려 없음
현재 viewWillAppear에서 호출하는 방식이 이 네비게이션 흐름에 더 적합하다고 생각합니다. 사용자가 선택지를 바꿀 때마다 올바르게 서버에 반영되어야 하니까요.
좋은 지적 감사합니다! 🐰
/| /|
( :v: :v: )
| > |
| |
/ \
✏️ Learnings added
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.002Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.860Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.859Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
| } catch { | ||
| BitnagilLogger.log(logType: .error, message: "\(error.localizedDescription)") | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 발생 시 UI 피드백 필요
온보딩 등록 실패 시 로그만 남기고 있어 사용자는 문제를 인지할 수 없습니다. 에러 상태를 UI에 전달하는 메커니즘을 추가하세요.
+let errorPublisher: AnyPublisher<String?, Never>
+private let errorSubject = PassthroughSubject<String?, Never>()
// In init
+errorPublisher: errorSubject.eraseToAnyPublisher()
// In catch block
} catch {
BitnagilLogger.log(logType: .error, message: "\(error.localizedDescription)")
+ errorSubject.send("온보딩 등록에 실패했습니다. 다시 시도해주세요.")
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift
around lines 210 to 212, the catch block only logs the error without providing
any UI feedback. Modify the code to propagate the error state to the UI, for
example by updating a published error property or calling a delegate method, so
the user is informed of the onboarding registration failure.
| Task { | ||
| do { | ||
| let entities = try await onboardingUseCase.registerOnboarding(onboardingChoices: onboardingChoices) | ||
| let recommendedRoutines = entities.map({ $0.toRecommendedRoutine() }) | ||
| recommendedRoutineSubject.send([]) | ||
| recommendedRoutineSubject.send(Set(recommendedRoutines)) | ||
| } catch { | ||
| BitnagilLogger.log(logType: .error, message: "\(error.localizedDescription)") | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Task 취소 처리 추가 필요
사용자가 화면을 빠르게 이동하거나 뷰가 해제될 때 진행 중인 Task를 취소할 수 있도록 처리가 필요합니다.
+private var onboardingTask: Task<Void, Never>?
+
private func registerOnboarding() {
// ... existing code ...
+ onboardingTask?.cancel()
- Task {
+ onboardingTask = Task {
do {
let entities = try await onboardingUseCase.registerOnboarding(onboardingChoices: onboardingChoices)
+ guard !Task.isCancelled else { return }
let recommendedRoutines = entities.map({ $0.toRecommendedRoutine() })
recommendedRoutineSubject.send([])
recommendedRoutineSubject.send(Set(recommendedRoutines))
} catch {
+ guard !Task.isCancelled else { return }
BitnagilLogger.log(logType: .error, message: "\(error.localizedDescription)")
}
}
}
+
+deinit {
+ onboardingTask?.cancel()
+}🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift
around lines 204 to 214, the asynchronous Task does not handle cancellation,
which can cause issues if the user navigates away or the view is deallocated.
Refactor the code to store a reference to the Task and cancel it appropriately
when the view disappears or is deinitialized. This ensures that any ongoing
asynchronous work is properly cancelled to avoid unnecessary processing or
memory leaks.
| guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self) | ||
| else { return } | ||
| let userEntity = userResponse.toUserEntity() |
There was a problem hiding this comment.
nil 응답 시 에러를 던져야 함
reissueToken() 메서드에서 서버 응답이 nil일 때 조용히 return하는 것은 문제가 될 수 있습니다. 토큰 재발급 실패는 중요한 에러 상황이므로 명시적으로 처리해야 합니다.
다음과 같이 수정하세요:
- guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self)
- else { return }
+ guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self)
+ else { throw AuthError.invalidUserData }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self) | |
| else { return } | |
| let userEntity = userResponse.toUserEntity() | |
| guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self) | |
| else { throw AuthError.invalidUserData } | |
| let userEntity = userResponse.toUserEntity() |
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/AuthRepository.swift around lines 72
to 74, the code silently returns when the server response is nil in the
reissueToken() method. Instead, modify the guard statement to throw a specific
error when userResponse is nil, ensuring that token reissue failures are
explicitly handled and propagated as errors.
| do { | ||
| guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self) | ||
| else { throw AuthError.invalidUserData } | ||
|
|
||
| let userEntity = userResponse.toUserEntity() | ||
| guard | ||
| saveToken(tokenType: .accessToken, token: userEntity.accessToken), | ||
| saveToken(tokenType: .refreshToken, token: userEntity.refreshToken) | ||
| else { throw AuthError.tokenSaveFailed } | ||
|
|
||
| BitnagilLogger.log(logType: .debug, message: "User Logined: \(userEntity.userState)") | ||
| BitnagilLogger.log(logType: .debug, message: "AccessToken Saved: \(userEntity.accessToken)") | ||
| BitnagilLogger.log(logType: .debug, message: "RefreshToken Saved: \(userEntity.refreshToken)") | ||
|
|
||
| return userEntity | ||
| } catch { | ||
| throw error | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
불필요한 do-catch 블록과 에러 로깅 누락
requestServerLogin 메서드의 에러 처리는 개선되었지만, 두 가지 문제가 있습니다:
- catch 블록에서 단순히 에러를 재발생시키는 것은 불필요합니다
- 로그인 실패 시 디버깅을 위한 에러 로그가 없습니다
다음과 같이 수정하세요:
- do {
- guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self)
- else { throw AuthError.invalidUserData }
-
- let userEntity = userResponse.toUserEntity()
- guard
- saveToken(tokenType: .accessToken, token: userEntity.accessToken),
- saveToken(tokenType: .refreshToken, token: userEntity.refreshToken)
- else { throw AuthError.tokenSaveFailed }
-
- BitnagilLogger.log(logType: .debug, message: "User Logined: \(userEntity.userState)")
- BitnagilLogger.log(logType: .debug, message: "AccessToken Saved: \(userEntity.accessToken)")
- BitnagilLogger.log(logType: .debug, message: "RefreshToken Saved: \(userEntity.refreshToken)")
-
- return userEntity
- } catch {
- throw error
- }
+ guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self)
+ else {
+ BitnagilLogger.log(logType: .error, message: "Server login failed: No response received")
+ throw AuthError.invalidUserData
+ }
+
+ let userEntity = userResponse.toUserEntity()
+ guard
+ saveToken(tokenType: .accessToken, token: userEntity.accessToken),
+ saveToken(tokenType: .refreshToken, token: userEntity.refreshToken)
+ else {
+ BitnagilLogger.log(logType: .error, message: "Failed to save tokens for user: \(userEntity.userState)")
+ throw AuthError.tokenSaveFailed
+ }
+
+ BitnagilLogger.log(logType: .debug, message: "User Logined: \(userEntity.userState)")
+ BitnagilLogger.log(logType: .debug, message: "AccessToken Saved: \(userEntity.accessToken)")
+ BitnagilLogger.log(logType: .debug, message: "RefreshToken Saved: \(userEntity.refreshToken)")
+
+ return userEntity📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| do { | |
| guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self) | |
| else { throw AuthError.invalidUserData } | |
| let userEntity = userResponse.toUserEntity() | |
| guard | |
| saveToken(tokenType: .accessToken, token: userEntity.accessToken), | |
| saveToken(tokenType: .refreshToken, token: userEntity.refreshToken) | |
| else { throw AuthError.tokenSaveFailed } | |
| BitnagilLogger.log(logType: .debug, message: "User Logined: \(userEntity.userState)") | |
| BitnagilLogger.log(logType: .debug, message: "AccessToken Saved: \(userEntity.accessToken)") | |
| BitnagilLogger.log(logType: .debug, message: "RefreshToken Saved: \(userEntity.refreshToken)") | |
| return userEntity | |
| } catch { | |
| throw error | |
| } | |
| } | |
| guard let userResponse = try await networkService.request(endpoint: endpoint, type: LoginResponseDTO.self) | |
| else { | |
| BitnagilLogger.log(logType: .error, message: "Server login failed: No response received") | |
| throw AuthError.invalidUserData | |
| } | |
| let userEntity = userResponse.toUserEntity() | |
| guard | |
| saveToken(tokenType: .accessToken, token: userEntity.accessToken), | |
| saveToken(tokenType: .refreshToken, token: userEntity.refreshToken) | |
| else { | |
| BitnagilLogger.log(logType: .error, message: "Failed to save tokens for user: \(userEntity.userState)") | |
| throw AuthError.tokenSaveFailed | |
| } | |
| BitnagilLogger.log(logType: .debug, message: "User Logined: \(userEntity.userState)") | |
| BitnagilLogger.log(logType: .debug, message: "AccessToken Saved: \(userEntity.accessToken)") | |
| BitnagilLogger.log(logType: .debug, message: "RefreshToken Saved: \(userEntity.refreshToken)") | |
| return userEntity |
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/AuthRepository.swift around lines 118
to 136, remove the unnecessary do-catch block that only rethrows the error
without handling it. Instead, let the error propagate naturally. Additionally,
add error logging inside the catch block or before rethrowing to capture and log
the error details for debugging purposes when login fails.
There was a problem hiding this comment.
@choijungp 감사합니다! 불필요한 do-catch 블록을 제거하고 에러 로깅을 추가해주셔서 코드가 더 깔끔하고 디버깅하기 좋아졌네요. 수정 잘해주셨습니다! 👏
🧠 Learnings used
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.002Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
🌁 Background
이전 PR에서 구현한 온보딩 뷰와 서버 로직을 연동하였습니다.
온보딩 결과를 서버에 전송하여 온보딩 선택 기반으로 받은 추천 루틴 목록을 보여주는 것과, 추천 루틴을 선택해서 등록하는 기능을 연결하였습니다.
📱 Screenshot
온보딩 기반 추천 루틴 선택 ❌
온보딩 기반 추천 루틴 선택 ⭕️
👩💻 Contents
.guestvs.user) 이용약관 뷰와 온보딩 뷰 분기 처리✅ Testing
📣 Related Issue
Summary by CodeRabbit
신규 기능
기능 개선
버그 수정
리팩터링 및 구조 개선