-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] 네트워크 재시도 에러 View 구현 #85
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
Changes from all commits
48d16e3
dc31176
2f83e18
47a8f53
33489da
c3c2c5a
c2e269c
7df5255
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,11 +44,22 @@ final class LocationRepository: NSObject, LocationRepositoryProtocol { | |
|
|
||
| let endpoint = LocationEndpoint.fetchAddress(longitude: longitude, latitude: latitude) | ||
|
|
||
| guard let response = try await networkService.request(endpoint: endpoint, type: KakaoLocationResponseDTO.self) | ||
| else { return nil } | ||
| do { | ||
| guard let response = try await networkService.request(endpoint: endpoint, type: KakaoLocationResponseDTO.self) | ||
| else { return nil } | ||
|
|
||
| let location = response.toLocationEntity(fallbackLongitude: coordinate.longitude, fallbackLatitude: coordinate.latitude) | ||
| return location | ||
| let location = response.toLocationEntity(fallbackLongitude: coordinate.longitude, fallbackLatitude: coordinate.latitude) | ||
| return location | ||
| } catch let error as NetworkError { | ||
| switch error { | ||
| case .needRetry, .invalidURL, .emptyData: | ||
| throw DomainError.requireRetry | ||
| default: | ||
| throw DomainError.business(error.description) | ||
| } | ||
|
Comment on lines
+53
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # LocationRepository.swift 파일의 실제 내용 확인 (53-59줄 포함)
find . -name "LocationRepository.swift" -type fRepository: YAPP-Github/Bitnagil-iOS Length of output: 133 🏁 Script executed: # NetworkError 정의 확인
find . -name "*.swift" -type f | xargs grep -l "enum NetworkError" | head -5Repository: YAPP-Github/Bitnagil-iOS Length of output: 131 🏁 Script executed: # 다른 Repository 파일들에서 동일한 패턴 검색
find . -path "*/Repository/*" -name "*Repository.swift" -type f | head -10Repository: YAPP-Github/Bitnagil-iOS Length of output: 728 🏁 Script executed: # LocationRepository.swift 파일의 lines 47-62 확인
cat -n ./Projects/DataSource/Sources/Repository/LocationRepository.swift | sed -n '40,65p'Repository: YAPP-Github/Bitnagil-iOS Length of output: 1238 🏁 Script executed: # NetworkError 정의 확인
cat -n ./Projects/DataSource/Sources/NetworkService/NetworkError.swiftRepository: YAPP-Github/Bitnagil-iOS Length of output: 1311 🏁 Script executed: # 다른 Repository들의 에러 처리 패턴 확인
for file in ./Projects/DataSource/Sources/Repository/{AppConfigRepository,AuthRepository,EmotionRepository,FileRepository,OnboardingRepository,RecommendedRoutineRepository,ReportRepository,RoutineRepository,UserDataRepository}.swift; do
echo "=== $file ===";
grep -A 10 "catch let error as NetworkError" "$file" | head -15;
doneRepository: YAPP-Github/Bitnagil-iOS Length of output: 3602
이 문제는 LocationRepository에만 국한된 것이 아니라 EmotionRepository, FileRepository, OnboardingRepository, RecommendedRoutineRepository, ReportRepository, RoutineRepository 등 다수의 Repository에서 동일한 패턴으로 반복되고 있습니다. 이는 체계적인 설계 문제로, 다음과 같이 수정하는 것이 적절합니다: 제안하는 수정 } catch let error as NetworkError {
switch error {
- case .needRetry, .invalidURL, .emptyData:
+ case .needRetry:
throw DomainError.requireRetry
+ case .invalidURL, .emptyData:
+ throw DomainError.business(error.description)
default:
throw DomainError.business(error.description)
}이 수정은 LocationRepository뿐만 아니라 동일한 패턴을 사용하는 모든 Repository 파일에 적용되어야 합니다. 🤖 Prompt for AI Agents |
||
| } catch { | ||
| throw DomainError.unknown | ||
| } | ||
| } | ||
|
|
||
| private func requestAuthorizationIfNeeded() async -> CLAuthorizationStatus { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,11 +13,21 @@ final class OnboardingRepository: OnboardingRepositoryProtocol { | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| func loadOnboardingResult() async throws -> OnboardingEntity { | ||||||||||||||||||||||||||
| let endpoint = OnboardingEndpoint.loadOnboardingResult | ||||||||||||||||||||||||||
| guard let response = try await networkService.request(endpoint: endpoint, type: OnboardingResponseDTO.self) | ||||||||||||||||||||||||||
| else { throw UserError.onboardingLoadFailed } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let onboardingEntity = response.toOnboardingEntity() | ||||||||||||||||||||||||||
| return onboardingEntity | ||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||
| guard let response = try await networkService.request(endpoint: endpoint, type: OnboardingResponseDTO.self) | ||||||||||||||||||||||||||
| else { throw UserError.onboardingLoadFailed } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let onboardingEntity = response.toOnboardingEntity() | ||||||||||||||||||||||||||
| return onboardingEntity | ||||||||||||||||||||||||||
| } catch let error as NetworkError { | ||||||||||||||||||||||||||
| switch error { | ||||||||||||||||||||||||||
| case .needRetry, .invalidURL, .emptyData: | ||||||||||||||||||||||||||
| throw DomainError.requireRetry | ||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| throw DomainError.business(error.description) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+24
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Find the NetworkError enum definition
find . -name "*.swift" -type f | xargs grep -l "enum NetworkError"Repository: YAPP-Github/Bitnagil-iOS Length of output: 131 🏁 Script executed: # Look at the OnboardingRepository.swift file to see all error handling
cat -n Projects/DataSource/Sources/Repository/OnboardingRepository.swiftRepository: YAPP-Github/Bitnagil-iOS Length of output: 3369 🏁 Script executed: # Search for all occurrences of invalidURL case in the codebase
rg "case.*invalidURL" --type swift -B 2 -A 2Repository: YAPP-Github/Bitnagil-iOS Length of output: 11831 🏁 Script executed: # Find where NetworkError is defined and check its cases
rg "enum NetworkError" --type swift -A 20Repository: YAPP-Github/Bitnagil-iOS Length of output: 1986
이 패턴이 OnboardingRepository의 세 메서드 모두에 적용되어 있으며, 코드베이스의 다른 모든 Repository(RoutineRepository, FileRepository, ReportRepository 등)에서도 동일하게 반복되고 있으므로 함께 수정이 필요합니다. 🔧 수정 제안 } catch let error as NetworkError {
switch error {
- case .needRetry, .invalidURL, .emptyData:
+ case .needRetry, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+17
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
다른 모든 리포지토리 메서드( 🐛 일관성을 위한 수정 제안
} catch let error as NetworkError {
switch error {
case .needRetry, .invalidURL, .emptyData:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}
+ } catch let error as UserError {
+ throw error
+ } catch {
+ throw DomainError.unknown
}🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UserError.onboardingLoadFailed 를 그대로 throw 하기 위한 의도된 부분입니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
✏️ Learnings added
🧠 Learnings used |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| func registerOnboarding(onboardingEntity: OnboardingEntity) async throws -> [RecommendedRoutineEntity] { | ||||||||||||||||||||||||||
|
|
@@ -27,15 +37,39 @@ final class OnboardingRepository: OnboardingRepositoryProtocol { | |||||||||||||||||||||||||
| realOutingFrequency: onboardingEntity.frequency, | ||||||||||||||||||||||||||
| targetOutingFrequency: onboardingEntity.outdoor) | ||||||||||||||||||||||||||
| let endpoint = OnboardingEndpoint.registerOnboarding(onboarding: onboardingDTO) | ||||||||||||||||||||||||||
| guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self) | ||||||||||||||||||||||||||
| else { return [] } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() }) | ||||||||||||||||||||||||||
| return recommendedRoutineEntity | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||
| guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineListResponseDTO.self) | ||||||||||||||||||||||||||
| else { return [] } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let recommendedRoutineEntity = response.recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity() }) | ||||||||||||||||||||||||||
| return recommendedRoutineEntity | ||||||||||||||||||||||||||
| } catch let error as NetworkError { | ||||||||||||||||||||||||||
| switch error { | ||||||||||||||||||||||||||
| case .needRetry, .invalidURL, .emptyData: | ||||||||||||||||||||||||||
| throw DomainError.requireRetry | ||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| throw DomainError.business(error.description) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||
| throw DomainError.unknown | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| func registerRecommendedRoutines(selectedRoutines: [Int]) async throws { | ||||||||||||||||||||||||||
| let endpoint = OnboardingEndpoint.registerRecommendedRoutine(selectedRoutines: selectedRoutines) | ||||||||||||||||||||||||||
| _ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||
| _ = try await networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self) | ||||||||||||||||||||||||||
| } catch let error as NetworkError { | ||||||||||||||||||||||||||
| switch error { | ||||||||||||||||||||||||||
| case .needRetry, .invalidURL, .emptyData: | ||||||||||||||||||||||||||
| throw DomainError.requireRetry | ||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| throw DomainError.business(error.description) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||
| throw DomainError.unknown | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,23 +13,46 @@ final class RecommendedRoutineRepository: RecommendedRoutineRepositoryProtocol { | |||||||||||||||||||||||||
| func fetchRecommendedRoutine(id: Int) async throws -> RecommendedRoutineEntity? { | ||||||||||||||||||||||||||
| let endpoint = RecommendedRoutineEndpoint.fetchRecommendedRoutine(id: id) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| guard let recommendedRoutineDTO = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDTO.self) | ||||||||||||||||||||||||||
| else { return nil } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return recommendedRoutineDTO.toRecommendedRoutineEntity() | ||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||
| guard let recommendedRoutineDTO = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDTO.self) | ||||||||||||||||||||||||||
| else { return nil } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return recommendedRoutineDTO.toRecommendedRoutineEntity() | ||||||||||||||||||||||||||
| } catch let error as NetworkError { | ||||||||||||||||||||||||||
| switch error { | ||||||||||||||||||||||||||
| case .needRetry, .invalidURL, .emptyData: | ||||||||||||||||||||||||||
| throw DomainError.requireRetry | ||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| throw DomainError.business(error.description) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+22
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n Projects/DataSource/Sources/Repository/RecommendedRoutineRepository.swiftRepository: YAPP-Github/Bitnagil-iOS Length of output: 2549 🏁 Script executed: rg -n "\.needRetry.*\.invalidURL.*\.emptyData" --type swiftRepository: YAPP-Github/Bitnagil-iOS Length of output: 2614 🏁 Script executed: rg -n "case \.needRetry" --type swift -A 3Repository: YAPP-Github/Bitnagil-iOS Length of output: 10040
이 패턴은 RecommendedRoutineRepository 외에도 LocationRepository, RoutineRepository, ReportRepository, OnboardingRepository, FileRepository, EmotionRepository 등 9개 파일의 21개 위치에서 동일하게 사용되고 있어 프로젝트 전반에 걸친 개선이 필요합니다. 제안된 수정 } catch let error as NetworkError {
switch error {
- case .needRetry, .invalidURL, .emptyData:
+ case .needRetry:
throw DomainError.requireRetry
default:
throw DomainError.business(error.description)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||
| throw DomainError.unknown | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| func fetchRecommendedRoutines() async throws -> [RecommendedRoutineEntity] { | ||||||||||||||||||||||||||
| let endpoint = RecommendedRoutineEndpoint.fetchRecommendedRoutines | ||||||||||||||||||||||||||
| guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDictionaryResponseDTO.self) | ||||||||||||||||||||||||||
| else { return [] } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var entities: [RecommendedRoutineEntity] = [] | ||||||||||||||||||||||||||
| for (category, recommendedRoutines) in response.recommendedRoutines { | ||||||||||||||||||||||||||
| let recommendedRoutineEntity = recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity(category: category) }) | ||||||||||||||||||||||||||
| entities.append(contentsOf: recommendedRoutineEntity) | ||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||
| guard let response = try await networkService.request(endpoint: endpoint, type: RecommendedRoutineDictionaryResponseDTO.self) | ||||||||||||||||||||||||||
| else { return [] } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var entities: [RecommendedRoutineEntity] = [] | ||||||||||||||||||||||||||
| for (category, recommendedRoutines) in response.recommendedRoutines { | ||||||||||||||||||||||||||
| let recommendedRoutineEntity = recommendedRoutines.compactMap({ $0.toRecommendedRoutineEntity(category: category) }) | ||||||||||||||||||||||||||
| entities.append(contentsOf: recommendedRoutineEntity) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return entities | ||||||||||||||||||||||||||
| } catch let error as NetworkError { | ||||||||||||||||||||||||||
| switch error { | ||||||||||||||||||||||||||
| case .needRetry, .invalidURL, .emptyData: | ||||||||||||||||||||||||||
| throw DomainError.requireRetry | ||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| throw DomainError.business(error.description) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||
| throw DomainError.unknown | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return entities | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
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.
loadEmotion에서NetworkError를 직접 throw한 후 같은 메서드 내에서 다시 catch하는 것은 우회적입니다.Line 39에서 response가 nil일 때
NetworkError.unknown을 throw하는데, 이는 바로 아래catch let error as NetworkError에서 잡혀DomainError.business로 변환됩니다. 직접DomainError를 throw하는 것이 더 명확합니다. 또한 "Reponse"는 "Response"의 오타입니다.🛠️ 수정 제안
do { guard let response = try await networkService.request(endpoint: endpoint, type: EmotionResponseDTO.self) - else { throw NetworkError.unknown(description: "Emotion Reponse를 받아오지 못했습니다.") } + else { throw DomainError.business("Emotion Response를 받아오지 못했습니다.") } let emotionEntity = response.toEmotionEntity() return emotionEntity🤖 Prompt for AI Agents