[Feat] 제보하기 화면 구현(2차), 역지오코딩 로직 구현 - #66
Conversation
워크스루이 변경사항은 위치 기반 기능과 보고 시스템을 구현하기 위해 데이터 소스, 도메인, 프레젠테이션 레이어에 걸쳐 다양한 저장소, 사용 사례, UI 컴포넌트를 추가합니다. Kakao API를 통한 주소 해석, 기기 위치 조회, 보고서 제출 기능이 포함됩니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant VC as ReportViewController
participant VM as ReportViewModel
participant UC as ReportUseCase
participant LocRepo as LocationRepository
participant KakaoAPI as Kakao API
participant ReportRepo as ReportRepository
VC->>VM: configureLocation()
activate VM
VM->>UC: fetchCurrentLocation()
activate UC
UC->>LocRepo: fetchCoordinate()
activate LocRepo
LocRepo->>LocRepo: requestAuthorizationIfNeeded()
LocRepo->>LocRepo: await location updates
LocRepo-->>UC: LocationEntity (coord)
deactivate LocRepo
UC->>LocRepo: fetchAddress(coordinate:)
activate LocRepo
LocRepo->>KakaoAPI: GET /v2/local/geo/coord2address.json
KakaoAPI-->>LocRepo: KakaoLocationResponseDTO
LocRepo-->>UC: LocationEntity (with address)
deactivate LocRepo
UC-->>VM: LocationEntity
deactivate UC
VM->>VM: update locationPublisher
deactivate VM
VM-->>VC: location updated
VC->>VC: showCategoryBottomSheet()
VC->>VC: create ReportCategoryTableViewController
VC->>VC: set delegate
VC->>VC: present modal
VC->>VM: selectCategory(type:)
VM->>VM: update selectedReportType
예상 코드 검토 노력🎯 3 (Moderate) | ⏱️ ~25 분 주의가 필요한 영역:
시
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (13)
Projects/Presentation/Sources/Report/Model/ReportType+.swift (1)
24-35: 코드 구현이 정확하고 깔끔합니다.새로 추가된
name프로퍼티의 구현이 올바르며, 각 케이스에 대한 한글 문자열이 적절하게 매핑되어 있습니다.선택 사항: 다국어 지원을 고려한다면 로컬라이제이션 적용
현재는 한글 문자열이 하드코딩되어 있습니다. 만약 향후 다국어 지원 계획이 있다면
Localizable.strings파일을 활용하는 것을 고려해보세요:var name: String { switch self { case .transportation: NSLocalizedString("report.type.transportation", comment: "") case .lamp: NSLocalizedString("report.type.lamp", comment: "") case .water: NSLocalizedString("report.type.water", comment: "") case .convenience: NSLocalizedString("report.type.convenience", comment: "") } }Projects/DataSource/Sources/NetworkService/NetworkService.swift (1)
89-96: 에러 정보 보존을 위한 개선 제안폴백 디코딩 로직은 Kakao API와 같은 외부 API를 지원하기 위한 좋은 접근 방식입니다. 다만, 첫 번째 디코딩 시도에서 발생한 에러 정보가 손실되어 디버깅이 어려워질 수 있습니다. 두 디코딩 모두 실패할 경우, 첫 번째 에러의 상세 정보를 로깅하거나 보존하는 것을 권장합니다.
다음과 같이 개선할 수 있습니다:
} catch { + let baseResponseError = error do { let generalResponse = try decoder.decode(T.self, from: data) return generalResponse } catch { + // 디버깅을 위해 첫 번째 에러 정보를 로깅 + print("BaseResponse decoding failed: \(baseResponseError)") + print("Direct T decoding failed: \(error)") throw NetworkError.decodingError } }또는 더 상세한 에러 정보를 포함하도록 NetworkError를 확장할 수도 있습니다:
// NetworkError에 추가 case decodingError(baseResponseError: Error?, directError: Error?)} catch { + let baseResponseError = error do { let generalResponse = try decoder.decode(T.self, from: data) return generalResponse } catch { - throw NetworkError.decodingError + throw NetworkError.decodingError( + baseResponseError: baseResponseError, + directError: error + ) } }Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift (1)
8-10: 프로토콜 정의는 적절하나 구현 필요프로토콜 정의는 올바르지만,
Projects/DataSource/Sources/Repository/ReportRepository.swift의 구현이 비어있습니다. 제보 기능을 완성하려면 실제 API 호출 로직을 구현해야 합니다.API 엔드포인트와 요청/응답 모델 구현을 도와드릴까요?
Projects/Domain/Sources/Entity/LocationEntity.swift (1)
8-22: Equatable/Hashable 프로토콜 준수 고려LocationEntity를 비교하거나 Set, Dictionary에서 사용할 가능성이 있다면 Equatable 및 Hashable 프로토콜 준수를 고려하세요.
-public struct LocationEntity { +public struct LocationEntity: Equatable, Hashable { public let longitude: Double public let latitude: Double public let address: String?Projects/DataSource/Sources/Repository/ReportRepository.swift (1)
10-13: report 메서드 구현 필요ReportRepository의 report 메서드가 비어있습니다. 실제 제보 API 호출 로직을 구현해야 합니다.
구현 예시 또는 TODO 추적을 위한 이슈 생성을 도와드릴까요?
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
23-25: report 메서드 구현 필요report 메서드가 비어있습니다. 실제 제보 로직 구현이 필요합니다.
실제 구현 예시나 TODO 추적을 위한 이슈 생성을 도와드릴까요?
Projects/DataSource/Sources/Endpoint/LocationEndpoint.swift (1)
13-25: Kakao 주소 조회 URL 조합 방식 재확인 필요
baseURL가"https://dapi.kakao.com/v2"이고,path에서 다시baseURL + "/local/geo/coord2address.json"를 반환하고 있어서,NetworkService가baseURL + path를 조합하는 구조라면 실제 요청 URL이 이중으로 붙을 위험이 있습니다.
프로젝트 내 다른Endpoint구현과 동일한 패턴인지 한 번만 확인 부탁드립니다.Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (2)
33-56: 선언부 정리:location의= nil초기화는 제거 가능
private var location: LocationEntity? = nil은 옵셔널 기본값이 원래nil이기 때문에 초기화 코드를 생략해도 동일하게 동작합니다. SwiftLint 경고(redundant_optional_initialization)도 이 부분을 지적하고 있으니, 다음처럼 정리하는 쪽이 깔끔합니다.- private var location: LocationEntity? = nil + private var location: LocationEntity?Also applies to: 42-42
90-100: 위치 조회 에러 처리 및 상태 갱신 방식 보완 제안
configureLocation()에서reportUseCase.getCurrentLocation()의 에러를catch에서 완전히 무시하고, 이후에locationSubject.send(location?.address)만 호출하고 있습니다. 이 경우:
- 새 위치 조회에 실패해도 이전에 남아 있던
location값이 다시 방출될 수 있고,- 사용자에게 실패 원인이나 안내 메시지가 전달되지 않습니다.
예를 들어 다음과 같이 에러 시에는
location을nil로 초기화하고,exceptionSubject를 통해 안내 문구를 전달하는 방식을 고려해 보시는 것을 권장드립니다.Task { do { self.location = try await reportUseCase.getCurrentLocation() } catch { self.location = nil exceptionSubject.send("현재 위치를 가져오지 못했습니다. 위치 권한을 확인해 주세요.") } locationSubject.send(location?.address) }Projects/DataSource/Sources/Repository/LocationRepository.swift (1)
24-37:getCoordinate()의 동시 호출 시 단일continuation사용으로 인한 경쟁 상태 가능성
getCoordinate()는continuation프로퍼티 하나를 사용해 위치 응답을 대기하고 있어서, 동시에 여러 곳에서getCoordinate()를 호출하면 마지막 호출만 정상적으로resume되고 이전 호출용 continuation은 덮어써질 수 있습니다.현재 사용 패턴상 동시에 여러 호출이 발생하지 않는다면 큰 문제는 아니겠지만, 방어적으로는:
continuation이 이미 존재하면 바로nil을 반환하거나 에러를 표현,- 혹은 내부적으로 큐를 두고 순차적으로 처리
하는 방식을 고려해 볼 수 있습니다.
Projects/Presentation/Sources/Report/View/ReportCategoryTableViewController.swift (1)
27-38: 뷰 구성 시점:init대신viewDidLoad에서 설정하는 패턴 고려현재 이 뷰 컨트롤러는
init(reportType:)에서 바로configureAttribute()와configureLayout()을 호출하면서view.addSubview(categoryTableView)까지 수행하고 있습니다. Swift에서는UIViewController의view를 초기화 직후 바로 건드려도 동작은 하지만, 일반적으로는:
- 뷰 계층 구성(
addSubview, 제약 설정)- UI 속성 설정(delegate, dataSource 등)
을
viewDidLoad()에서 처리하는 패턴을 더 많이 사용합니다.크게 문제 되지는 않지만, 다른 뷰 컨트롤러들과 일관성과 가독성을 위해 아래처럼 옮기는 것도 고려해 볼 수 있습니다.
init(reportType: ReportType?) { self.selectedCategory = reportType super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() configureAttribute() configureLayout() }Also applies to: 39-60
Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift (2)
8-13:Foundation/Domain중복 import 정리이 파일 상단에서
Foundation,Domain을 두 번씩 import 하고 있어 SwiftLint의duplicate_imports경고가 발생하고 있습니다. 아래처럼 한 번만 import 하도록 정리하면 됩니다.-import Domain -import Foundation - -import Foundation -import Domain +import Foundation +import Domain(또는 프로젝트 컨벤션에 맞게 순서만 맞추시면 됩니다.)
132-141: Kakao 응답 →LocationEntity매핑 로직은 안정적으로 보입니다
toLocationEntity(fallbackLongitude:fallbackLatitude:)에서
- 첫 번째
documents.first만 사용하고,- 문서의
longitude/latitude가 없으면 전달받은 fallback 좌표를 사용하며,- 주소는 도로명 주소 → 지번 주소 순으로 선택
하는 전략은 실제 Kakao
coord2address응답 구조를 고려했을 때 자연스럽고, 실패 시nil을 반환해 상위 레이어에서 처리할 수 있게 한 것도 좋습니다.추가로, 좌표와 주소를 모두 얻지 못한 경우에 한해
0좌표로LocationEntity를 만드는 대신nil을 반환하도록 바꾸는 것도 한 번 검토해 볼 만합니다(지도 상에서 (0,0)으로 이동하는 부자연스러운 상황 방지).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/Common/Enum/AppProperties.swift(1 hunks)Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/LocationEndpoint.swift(1 hunks)Projects/DataSource/Sources/NetworkService/NetworkService.swift(1 hunks)Projects/DataSource/Sources/Repository/LocationRepository.swift(1 hunks)Projects/DataSource/Sources/Repository/ReportRepository.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(1 hunks)Projects/Domain/Sources/Entity/Enum/ReportType.swift(1 hunks)Projects/Domain/Sources/Entity/LocationEntity.swift(1 hunks)Projects/Domain/Sources/Entity/ReportEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/LocationRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/ReportUseCaserProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Report/Model/ReportType+.swift(1 hunks)Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift(1 hunks)Projects/Presentation/Sources/Report/View/Component/ReportTextView.swift(1 hunks)Projects/Presentation/Sources/Report/View/ReportCategoryTableViewController.swift(1 hunks)Projects/Presentation/Sources/Report/View/ReportViewController.swift(4 hunks)Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift(5 hunks)SupportingFiles/Info.plist(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
Projects/Domain/Sources/Protocol/UseCase/ReportUseCaserProtocol.swift (2)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (2)
getCurrentLocation(17-21)report(23-25)Projects/DataSource/Sources/Repository/ReportRepository.swift (1)
report(11-13)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (2)
Projects/DataSource/Sources/Repository/LocationRepository.swift (2)
getCoordinate(24-37)getAddress(39-47)Projects/DataSource/Sources/Repository/ReportRepository.swift (1)
report(11-13)
Projects/DataSource/Sources/Repository/ReportRepository.swift (1)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
report(23-25)
Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift (1)
Projects/Presentation/Sources/Report/View/ReportCategoryTableViewController.swift (2)
configureAttribute(39-44)configureLayout(46-60)
Projects/Domain/Sources/Protocol/Repository/LocationRepositoryProtocol.swift (1)
Projects/DataSource/Sources/Repository/LocationRepository.swift (2)
getCoordinate(24-37)getAddress(39-47)
Projects/Presentation/Sources/Report/View/ReportCategoryTableViewController.swift (2)
Projects/Presentation/Sources/Report/View/ReportViewController.swift (3)
reportCategoryTableViewController(481-484)configureAttribute(72-117)configureLayout(119-312)Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift (3)
configureAttribute(45-55)configureLayout(57-93)configureCell(95-102)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/Domain/Sources/Protocol/Repository/ReportRepositoryProtocol.swift (2)
Projects/DataSource/Sources/Repository/ReportRepository.swift (1)
report(11-13)Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
report(23-25)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
resolve(18-25)
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (1)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
getCurrentLocation(17-21)
Projects/DataSource/Sources/Repository/LocationRepository.swift (2)
Projects/DataSource/Sources/NetworkService/NetworkService.swift (1)
request(25-48)Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift (1)
toLocationEntity(134-141)
Projects/Presentation/Sources/Report/View/ReportViewController.swift (2)
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (3)
action(58-75)configureLocation(90-100)register(122-124)Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
presentCustomBottomSheet(98-101)
🪛 SwiftLint (0.57.0)
Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift
[Warning] 11-11: Imports should be unique
(duplicate_imports)
[Warning] 12-12: Imports should be unique
(duplicate_imports)
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift
[Warning] 42-42: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (20)
Projects/Presentation/Sources/Report/Model/ReportType+.swift (1)
37-48: 설명 문자열이 각 카테고리에 적합합니다.각 제보 유형에 대한 설명이 구체적이고 이해하기 쉽게 작성되었습니다. 사용자가 어떤 카테고리를 선택해야 할지 판단하는 데 도움이 될 것입니다.
Projects/DataSource/Sources/NetworkService/NetworkService.swift (1)
84-88: 변수명 변경 및 로직 승인
baseResponse에서bitnagilResponse로의 변수명 변경은 프로젝트의 BaseResponse 타입과의 연관성을 명확히 하여 가독성을 향상시킵니다. 데이터 접근 로직도 올바르게 처리되고 있습니다.Projects/DataSource/Sources/Common/Enum/AppProperties.swift (1)
19-21: LGTM!Kakao API 키 속성이 올바르게 추가되었습니다. 기존 패턴(
kakaoNativeKey)을 따르고 있으며, Info.plist에서 값을 가져오는 방식이 일관적입니다.SupportingFiles/Info.plist (2)
5-6: LGTM!위치 권한 요청 메시지가 한국어로 적절하게 추가되었습니다.
42-43: LGTM!Kakao API 키가 환경 변수로부터 올바르게 설정되었습니다. 기존
KakaoNativeKey패턴과 일관성 있게 구현되었습니다.Projects/Presentation/Sources/Report/View/Component/ReportTextView.swift (1)
29-30: 셰브론 이미지 크기 변경 확인셰브론 이미지 크기가 10x6에서 24x24로 크게 증가했습니다(너비 140% 증가, 높이 300% 증가). PR 설명에 다양한 디바이스 크기의 스크린샷이 포함되어 있다고 하니, 모든 디바이스에서 레이아웃이 의도대로 표시되는지 확인해주세요.
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
44-50: LGTM!LocationRepository와 ReportRepository의 DI 등록이 올바르게 추가되었습니다. 기존 패턴과 일관성 있게 구현되었습니다.
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
66-73: LGTM!ReportUseCase의 DI 등록이 올바르게 구현되었습니다. 필요한 의존성(LocationRepository, ReportRepository)을 적절히 주입하고 있으며, 에러 처리도 명확합니다.
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
124-129: LGTM!ReportViewModel의 DI 등록이 올바르게 추가되었습니다. ReportUseCaserProtocol을 적절히 주입하고 있으며, 기존 패턴과 일관성이 있습니다.
Projects/Domain/Sources/Protocol/UseCase/ReportUseCaserProtocol.swift (1)
8-12: 프로토콜 정의 확인UseCase 프로토콜이 적절하게 정의되어 있습니다. getCurrentLocation은 throwing 함수로, report는 비동기 함수로 선언되어 의도가 명확합니다.
Projects/Domain/Sources/Protocol/Repository/LocationRepositoryProtocol.swift (1)
8-12: 프로토콜 메서드 시그니처 확인getCoordinate는 non-throwing이고 getAddress는 throwing입니다. 이는 의도적인 설계로 보이며(좌표 획득 실패는 nil 반환, 주소 변환 실패는 에러 던지기), 호출자가 각각 적절하게 처리하는지 확인하세요.
Projects/Presentation/Sources/Report/View/ReportViewController.swift (4)
41-41: 카테고리 바텀시트 높이 증가 확인categoryBottomSheetHeight가 226에서 362로 증가했습니다. 이는 추가된 카테고리 항목(transportation, water, convenience)을 수용하기 위한 것으로 보이며 적절합니다.
101-113: 버튼 액션 연결 확인locationButton은 위치 조회(configureLocation)를, registerButton은 제보 등록(register)을 트리거하도록 올바르게 변경되었습니다.
367-371: 카테고리 선택 뷰 생성 방식 변경 확인카테고리 선택 뷰를 프로퍼티에서 메서드 내 로컬 인스턴스로 변경했습니다. 메모리 효율성이 개선되었으며 delegate도 적절히 설정되어 있습니다.
480-485: ReportCategoryTableViewControllerDelegate 구현 확인새로운 delegate 메서드가 추가되어 카테고리 선택 시 viewModel로 전달합니다. 플로우가 명확하고 적절합니다.
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
17-21: getCurrentLocation 에러 처리 확인getCurrentLocation에서 getAddress가 실패하면 에러가 호출자에게 전파됩니다. ViewModel(ReportViewModel.configureLocation)에서 catch 블록이 비어있는데, 사용자에게 에러를 알리는 로직이 필요한지 확인하세요.
Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift (2)
95-102: 셀 구성 메서드 확인configureCell 메서드가 적절하게 구현되어 있으며, 선택 상태에 따라 체크 아이콘을 표시합니다.
105-118: 아이콘 매핑 검증 필요ReportType과 아이콘의 매핑이 의미적으로 일치하는지 확인하세요. 예를 들어:
- transportation → restIcon
- lamp → outsideIcon
- water → wakeupIcon
- convenience → growIcon
PR 설명에서 아이콘이 변경될 수 있다고 언급되었으므로, 최종 아이콘 리소스와 매핑을 재확인하세요.
Projects/Domain/Sources/Entity/Enum/ReportType.swift (1)
8-13: 제거된 ReportType 케이스 참조 없음 - 검증 완료코드베이스 전체를 검색한 결과, 제거된 케이스
road와etc에 대한 참조가 없습니다. 현재 enum의 4개 케이스(transportation, lamp, water, convenience)만 사용되고 있으며,ReportType+.swift의 모든 switch 문이 이들 케이스를 완벽하게 처리하고 있습니다.Projects/DataSource/Sources/Repository/LocationRepository.swift (1)
39-47: Kakao 주소 변환 로직 구성은 자연스럽습니다
getAddress(coordinate:)에서LocationEndpoint.fetchAddress를 사용해 Kakao API를 호출하고, 응답을KakaoLocationResponseDTO.toLocationEntity(fallbackLongitude:fallbackLatitude:)로 매핑하는 구조는 도메인LocationEntity와 잘 분리되어 있어 유지보수 측면에서 좋아 보입니다.좌표를 fallback으로 넘겨 두어 Kakao 응답에 좌표가 없더라도 최소한 기존 좌표를 유지하는 점도 합리적입니다.
| private func requestAuthorizationIfNeeded() async -> CLAuthorizationStatus { | ||
| let currentStatus = locationManager.authorizationStatus | ||
|
|
||
| switch currentStatus { | ||
| case .authorizedAlways, .authorizedWhenInUse: | ||
| return currentStatus | ||
| case .denied, .restricted: | ||
| return currentStatus | ||
| case .notDetermined: | ||
| return await withCheckedContinuation { (continuation: CheckedContinuation<CLAuthorizationStatus, Never>) in | ||
| self.authContinuation?.resume(returning: currentStatus) | ||
| self.authContinuation = continuation | ||
| self.locationManager.requestWhenInUseAuthorization() | ||
| } | ||
| default: | ||
| return currentStatus | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
위치 권한 요청용 authContinuation이 재개되지 않아 첫 호출이 영원히 대기 상태가 될 수 있습니다
requestAuthorizationIfNeeded()에서 .notDetermined인 경우 withCheckedContinuation을 사용해 authContinuation을 저장하고 requestWhenInUseAuthorization()을 호출하지만, 어디에서도 authContinuation을 resume하지 않고 있습니다.
그 결과:
- 사용자가 권한 허용/거부를 선택해도
await requestAuthorizationIfNeeded()가 깨어나지 않아, getCoordinate()첫 호출이 영원히 대기 상태에 빠질 수 있습니다.
보통은 CLLocationManagerDelegate의 권한 변경 콜백에서 authContinuation을 재개해야 합니다. 예를 들어 아래와 같이 보완하는 것을 권장드립니다.
extension LocationRepository: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
authContinuation?.resume(returning: status)
authContinuation = nil
}
// 기존 didUpdateLocations / didFailWithError 유지
}또한 requestAuthorizationIfNeeded() 내부의
self.authContinuation?.resume(returning: currentStatus)
self.authContinuation = continuation부분은 이전 continuation을 안전하게 정리하려는 의도라면, 실제로 .notDetermined가 아닌 상태에서만 호출되도록 하거나, 중복 resume이 일어나지 않도록 좀 더 명시적으로 처리해 주시는 것이 좋겠습니다.
Also applies to: 14-15, 69-81
---
🏁 Script executed:
```shell
#!/bin/bash
# Locate and examine LocationRepository.swift
fd -t f "LocationRepository.swift" -x cat {}
Length of output: 3004
🏁 Script executed:
#!/bin/bash
# Search for authContinuation usage and CLLocationManagerDelegate in the file
rg -n "authContinuation|locationManagerDidChangeAuthorization|CLLocationManagerDelegate" "Projects/DataSource/Sources/Repository/"Length of output: 535
🏁 Script executed:
#!/bin/bash
# Check the full context around lines 14-15, 49-66, and 69-81
rg -n -B2 -A8 "requestAuthorizationIfNeeded|func locationManager" "Projects/DataSource/Sources/Repository/LocationRepository.swift"Length of output: 1514
authContinuation을 재개하는 locationManagerDidChangeAuthorization 델리게이트 메서드가 없어 권한 요청 시 무한 대기가 발생합니다
검증 결과, 리뷰 의견이 정확합니다:
requestAuthorizationIfNeeded()메서드(49-66줄)의.notDetermined케이스에서withCheckedContinuation으로authContinuation을 설정하고requestWhenInUseAuthorization()을 호출합니다.- 그러나
CLLocationManagerDelegate확장(69-81줄)에는didUpdateLocations와didFailWithError만 구현되어 있고,locationManagerDidChangeAuthorization(_:)메서드가 없습니다. - 따라서 사용자가 권한을 승인/거부해도
authContinuation이 재개되지 않으므로await requestAuthorizationIfNeeded()는 영원히 대기 상태에 빠집니다.
추가로 59-60줄의 로직도 문제가 있습니다:
self.authContinuation?.resume(returning: currentStatus) // 이전 continuation 재개 시도
self.authContinuation = continuation // 새 continuation 설정첫 호출 시 authContinuation이 nil이면 59줄은 아무 작업도 하지 않으며, 중복 resume을 방지하는 명시적 처리가 부족합니다.
해결 방법:
CLLocationManagerDelegate 확장에 다음 메서드를 추가하세요:
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
authContinuation?.resume(returning: status)
authContinuation = nil
}또한 59-60줄을 정리하여 명확하게 변경하세요.
🤖 Prompt for AI Agents
Projects/DataSource/Sources/Repository/LocationRepository.swift lines 49-66: the
.notDetermined branch sets authContinuation and calls
requestWhenInUseAuthorization but there is no delegate callback to resume that
continuation, causing an indefinite await; add a CLLocationManagerDelegate
method locationManagerDidChangeAuthorization(_:) that reads
manager.authorizationStatus, resumes authContinuation with that status and then
sets authContinuation = nil, and simplify the .notDetermined logic by removing
the prior resume attempt (do not resume any previous continuation before
assigning the new one) so only the delegate resumes the stored continuation.
There was a problem hiding this comment.
래빗아 .. Continuation 넘 어렵다 .....................
There was a problem hiding this comment.
@choijungp 이해합니다! Continuation은 처음 접하면 정말 어려운 개념이에요. 쉽게 설명해드릴게요! 😊
🎫 Continuation을 식당 대기표로 생각해보세요
1. 손님이 식당에 가서 대기표를 받습니다 (withCheckedContinuation)
2. 대기표를 들고 기다립니다 (await)
3. 식당에서 호명을 하면 들어갈 수 있습니다 (resume)
현재 코드의 문제:
- 대기표를 받았는데 (authContinuation 저장)
- 식당에서 절대 호명을 안 해요! (resume이 없음)
- 손님은 영원히 기다립니다 😭
해결 방법 (간단 버전)
// 1️⃣ 권한 요청할 때 대기표 받기
private func requestAuthorizationIfNeeded() async -> CLAuthorizationStatus {
// ... 기존 코드 ...
case .notDetermined:
return await withCheckedContinuation { continuation in
self.authContinuation = continuation // 대기표 보관
self.locationManager.requestWhenInUseAuthorization() // 권한 요청
}
}
// 2️⃣ 권한 변경될 때 호명하기 - 이게 없어서 문제였어요!
extension LocationRepository: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// 권한이 변경되면 대기 중인 사람을 깨워줍니다
authContinuation?.resume(returning: manager.authorizationStatus)
authContinuation = nil // 대기표 처리 완료
}
// 기존 메서드들...
}이렇게 하면 사용자가 권한을 허용/거부하는 순간 locationManagerDidChangeAuthorization이 자동으로 호출되고, 여기서 resume을 해줘서 기다리던 코드가 계속 실행됩니다!
천천히 이해하시면 됩니다. 궁금한 점 있으면 언제든 물어보세요! 💪
🧠 Learnings used
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
| public struct ReportEntity { | ||
|
|
||
| } |
There was a problem hiding this comment.
ReportEntity 구조체 구현 필요
현재 빈 구조체로 정의되어 있습니다. 제보 기능을 완성하려면 필요한 속성들(예: 위치 정보, 카테고리, 설명, 이미지 등)을 정의해야 합니다.
제보 데이터 모델의 속성 정의를 도와드릴까요? 어떤 정보들을 포함해야 하는지 알려주시면 구조체 구현을 제안해드리겠습니다.
🤖 Prompt for AI Agents
In Projects/Domain/Sources/Entity/ReportEntity.swift around lines 8–10, the
ReportEntity struct is empty; implement it with the typical report fields: add
an id (UUID/String), reporterId (optional UUID/String for anonymous reports),
location fields (latitude: Double, longitude: Double, optional address: String),
category (define a Category enum conforming to String/Codable), description/body
(String), images (array of URL or String identifiers), createdAt/updatedAt
(Date), status (e.g., enum for New/InProgress/Resolved), and any flags like
isAnonymous (Bool); make the struct conform to Codable, Equatable (and
Identifiable if using SwiftUI), and provide a memberwise initializer or default
values where appropriate.
There was a problem hiding this comment.
서버 연동할 때 채울 예정이야 ~~ 돈와리 래빗 ~
There was a problem hiding this comment.
@choijungp, 알겠습니다! 서버 연동 시점에 구현하시는 것이 합리적이네요. API 스펙이 확정되면 그에 맞춰 필드를 정의하시면 되겠습니다. 👍
그때 필요하시면 언제든 도움 요청해주세요!
˙˙˙˙˙ ʕ •ᴥ•ʔ
˙˙˙˙ \( ̄︶ ̄)/
˙˙˙ "알겠어요!"
🧠 Learnings used
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
| case register | ||
| } |
There was a problem hiding this comment.
register 입력이 연결되어 있으나 실제 구현이 없어 제보 동작이 없습니다
Input.register가 action(input:)에서 register()로 정상적으로 연결되어 있지만, register() 본문이 비어 있어 사용자가 제보 버튼을 눌러도 아무 동작이 일어나지 않는 상태입니다.
- 이번 PR에서 아직 서버 연동까지 포함하지 않는 WIP라면, 최소한 TODO 주석으로 의도를 명시하거나,
- 실제 릴리즈 타깃이라면
selectedReportType,titleSubject,contentSubject,selectedPhotoSubject,location등을 이용해 도메인UseCase를 호출하는 구현이 필요해 보입니다.
원하시면 현재 상태를 기반으로 register() 내부에서 어떤 순서로 검증/제보 요청을 보내면 좋을지 구체적인 예시 코드를 같이 정리해 드릴 수 있습니다.
Also applies to: 72-74, 122-124
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift around
lines 20-21 (also applicable to 72-74 and 122-124), the register() function is
empty so Input.register triggers no action; either add a TODO comment if this is
WIP or implement the registration flow: validate selectedReportType,
titleSubject, contentSubject, selectedPhotoSubject and location (ensure required
fields present, enforce length/format rules), map subjects to the domain DTO,
call the domain UseCase/repository to perform the report submission, and handle
success/error by publishing appropriate outputs (loading state, success, and
error messages) back to the view. Ensure asynchronous calls are
awaited/scheduled correctly and errors are logged/propagated.
| <key>LSApplicationCategoryType</key> | ||
| <string></string> |
There was a problem hiding this comment.
🧩 Analysis chain
LSApplicationCategoryType 빈 값 확인 필요
LSApplicationCategoryType 키가 빈 문자열로 설정되어 있습니다. 이 키가 의도적으로 추가된 것인지, 특정 값이 필요한 것인지 확인이 필요합니다. 일반적으로 이 키는 macOS 앱에서 사용되며, iOS 앱에서는 불필요할 수 있습니다.
이 키가 필요한지, 필요하다면 적절한 값이 무엇인지 확인해주세요.
LSApplicationCategoryType 키는 정상 값으로 설정되어야 함 (현재 빈 문자열로 설정됨)
LSApplicationCategoryType은 macOS 전용 키이며 앱 타입에 해당하는 UTI를 포함하는 문자열입니다. 현재 빈 문자열로 설정되어 있는 것은 문제입니다:
-
macOS 앱인 경우: LSApplicationCategoryType에 적절한 값을 지정해야 하며, 유효한 카테고리 값(예:
public.app-category.productivity)으로 변경해야 합니다. 빈 값으로는 앱 검증 및 App Store 제출 시 오류가 발생합니다. -
iOS 전용 앱인 경우: 이 키는 필요하지 않으며 제거해야 합니다.
적절한 카테고리 값으로 수정하거나 iOS 앱이면 키 자체를 삭제해주세요.
🌐 Web query:
LSApplicationCategoryType Info.plist iOS macOS requirements
💡 Result:
Short answer
- LSApplicationCategoryType is a macOS Info.plist (Launch Services) key — NOT an iOS key. It must be present for Mac App Store submissions and its value must be one of Apple’s app-category UTIs (e.g. public.app-category.business, public.app-category.games, public.app-category.productivity, etc.). [1][4]
Details / requirements
- Platform: macOS (Launch Services). iOS does not use LSApplicationCategoryType. [1]
- Type: String key in the app’s top‑level Info.plist. [1]
- Value: Exactly one of the published UTIs Apple lists (public.app-category.*). Use the exact UTI string; the App Store uses it to categorize your app. [1][4]
- App Store requirement: The Mac App Store requires this key in Info.plist for submission. Xcode/project templates may add it but ensure it’s set for store builds. [4][5]
References
- Apple Info.plist Launch Services keys (LSApplicationCategoryType and UTI list). [1]
- “Submitting to the Mac App Store” / Categorize Your Application (requires LSApplicationCategoryType). [4]
If you want, I can paste the full list of valid public.app-category.* UTI strings.
LSApplicationCategoryType는 빈 문자열이 아닌 유효한 값으로 설정되어야 함
LSApplicationCategoryType은 macOS 전용 키입니다. 현재 빈 문자열로 설정되어 있는 것은 유효하지 않습니다:
-
macOS 앱인 경우: 이 키는 Mac App Store 제출 시 필수이며, Apple이 정의한 앱 카테고리 UTI 값(예:
public.app-category.productivity,public.app-category.business,public.app-category.games등)으로 정확히 설정되어야 합니다. 빈 값으로는 App Store 제출이 불가능합니다. -
iOS 전용 앱인 경우: 이 키는 iOS에서 사용되지 않으므로 제거해야 합니다.
적절한 카테고리 UTI 값으로 변경하거나, iOS 앱이면 키를 삭제해주세요.
🤖 Prompt for AI Agents
In SupportingFiles/Info.plist around lines 7 to 8, the LSApplicationCategoryType
key is set to an empty string which is invalid; if this is a macOS app, replace
the empty string with a valid Apple-defined category UTI such as
public.app-category.productivity or another appropriate public.app-category.*
value, otherwise (for an iOS-only app) remove the LSApplicationCategoryType key
entirely; update the plist accordingly and ensure the chosen value matches
Apple's permitted category UTIs before submitting to the App Store.
choijungp
left a comment
There was a problem hiding this comment.
따봉 띵똥 !!!!!!!!!!!!!!! 👍🏻👍🏻👍🏻👍🏻👍🏻
늦어서 죄송합니다 ㅠ.ㅠ. ......... 매우 감사함니다 .................
| import Foundation | ||
| import Domain |
| let generalResponse = try decoder.decode(T.self, from: data) | ||
| return generalResponse |
There was a problem hiding this comment.
요 부분이 리뷰 노트에 말씀해주신 부분이시죵 !! 완전 이해 완 ~~~
따봉 !!!
| locationManager.delegate = self | ||
| } | ||
|
|
||
| func getCoordinate() async -> LocationEntity? { |
There was a problem hiding this comment.
| func getCoordinate() async -> LocationEntity? { | |
| func fetchCoordinate() async -> LocationEntity? { |
| func getCoordinate() async -> LocationEntity? { | |
| func loadCoordinate() async -> LocationEntity? { |
| func getCoordinate() async -> LocationEntity? { | |
| func requestCoordinate() async -> LocationEntity? { |
저희 get / set 네이밍을 지양하자 라는 컨벤션이 있어가주구 요러한 네이밍은 어떠신지요 !!!
There was a problem hiding this comment.
악 요즘 자바하다보니까 깜빡했어요 ㅜㅜ 수정하겠습니다
| } | ||
| } | ||
|
|
||
| func getAddress(coordinate: LocationEntity) async throws -> LocationEntity? { |
There was a problem hiding this comment.
요기도 getAddress 대신 fetchAddress, loadAddress, requestAddress 어떠신지용 ? ?!? ?
| private func requestAuthorizationIfNeeded() async -> CLAuthorizationStatus { | ||
| let currentStatus = locationManager.authorizationStatus | ||
|
|
||
| switch currentStatus { | ||
| case .authorizedAlways, .authorizedWhenInUse: | ||
| return currentStatus | ||
| case .denied, .restricted: | ||
| return currentStatus | ||
| case .notDetermined: | ||
| return await withCheckedContinuation { (continuation: CheckedContinuation<CLAuthorizationStatus, Never>) in | ||
| self.authContinuation?.resume(returning: currentStatus) | ||
| self.authContinuation = continuation | ||
| self.locationManager.requestWhenInUseAuthorization() | ||
| } | ||
| default: | ||
| return currentStatus | ||
| } | ||
| } |
There was a problem hiding this comment.
래빗아 .. Continuation 넘 어렵다 .....................
| func report(reportEntity: Domain.ReportEntity) async { | ||
|
|
||
| } |
There was a problem hiding this comment.
요기는 추후 서버 연동할 부분인 것이지요 !!?
| public struct ReportEntity { | ||
|
|
||
| } |
There was a problem hiding this comment.
서버 연동할 때 채울 예정이야 ~~ 돈와리 래빗 ~
| // Created by 이동현 on 11/9/25. | ||
| // | ||
|
|
||
| public protocol ReportUseCaserProtocol { |
There was a problem hiding this comment.
| public protocol ReportUseCaserProtocol { | |
| public protocol ReportUseCaseProtocol { |
오타 발견 !!!!! 🚨🚨🚨🚨
There was a problem hiding this comment.
어떻게 찾았나 신기할 따름입니다,,
| // | ||
|
|
||
| public protocol ReportUseCaserProtocol { | ||
| func getCurrentLocation() async throws -> LocationEntity? |
There was a problem hiding this comment.
요기도 네이밍 수정이 필요할 것 같숨다 !!! load, fetch, request ... ......
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
23-25: 제보 기능 구현이 필요합니다.
report()메서드가 비어 있어 실제 제보 동작이 수행되지 않습니다.reportRepository.report(reportEntity:)를 호출하여 제보 로직을 완성해야 합니다.다음과 같이 구현하세요:
public func report(reportEntity: ReportEntity) async { - + await reportRepository.report(reportEntity: reportEntity) }Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (1)
122-124: 제보 기능 구현이 필요합니다.
register()메서드가 비어 있어 제보 버튼을 눌러도 아무 동작이 일어나지 않습니다. 필수 필드 검증 후reportUseCase.report()를 호출해야 합니다.다음과 같이 구현하세요:
private func register() { - + Task { + guard let selectedReportType = selectedReportType else { + exceptionSubject.send("카테고리를 선택해주세요.") + return + } + + guard let title = titleSubject.value, !title.isEmpty else { + exceptionSubject.send("제목을 입력해주세요.") + return + } + + guard let content = contentSubject.value, !content.isEmpty else { + exceptionSubject.send("내용을 입력해주세요.") + return + } + + let reportEntity = ReportEntity( + type: selectedReportType, + title: title, + content: content, + location: location, + photos: selectedPhotoSubject.value + ) + + await reportUseCase.report(reportEntity: reportEntity) + } }Projects/DataSource/Sources/Repository/LocationRepository.swift (1)
49-66: 권한 변경 델리게이트 메서드 구현이 필수입니다.
requestAuthorizationIfNeeded()의.notDetermined케이스에서authContinuation을 설정하고requestWhenInUseAuthorization()을 호출하지만, 이를 재개(resume)하는 델리게이트 메서드가 없어 무한 대기 상태가 발생합니다.또한 59-60번째 줄의 로직도 문제가 있습니다:
self.authContinuation?.resume(returning: currentStatus) // 첫 호출 시 nil이므로 동작하지 않음 self.authContinuation = continuation
CLLocationManagerDelegate에 다음 메서드를 추가하세요:func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { let status = manager.authorizationStatus authContinuation?.resume(returning: status) authContinuation = nil }그리고 59-60번째 줄을 제거하세요:
case .notDetermined: return await withCheckedContinuation { (continuation: CheckedContinuation<CLAuthorizationStatus, Never>) in - self.authContinuation?.resume(returning: currentStatus) self.authContinuation = continuation self.locationManager.requestWhenInUseAuthorization() }
🧹 Nitpick comments (1)
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (1)
42-42: 불필요한 nil 초기화를 제거하세요.Optional 변수를 명시적으로 nil로 초기화하는 것은 불필요합니다. Swift는 Optional 변수를 자동으로 nil로 초기화합니다.
-private var location: LocationEntity? = nil +private var location: LocationEntity?
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (12)
Projects/Presentation/Resources/Images.xcassets/Report/icon_car.imageset/icon_car.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_car.imageset/icon_car@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_car.imageset/icon_car@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_hammer.imageset/icon_hammer.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_hammer.imageset/icon_hammer@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_hammer.imageset/icon_hammer@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_light.imageset/icon_light.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_light.imageset/icon_light@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_light.imageset/icon_light@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_water.imageset/icon_water.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_water.imageset/icon_water@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Report/icon_water.imageset/icon_water@3x.pngis excluded by!**/*.png
📒 Files selected for processing (17)
Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift(1 hunks)Projects/DataSource/Sources/Repository/LocationRepository.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/LocationRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/ReportUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift(1 hunks)Projects/Presentation/Resources/Colors.xcassets/Blue/Blue300.colorset/Contents.json(1 hunks)Projects/Presentation/Resources/Colors.xcassets/Blue/Contents.json(1 hunks)Projects/Presentation/Resources/Colors.xcassets/Green/Green300.colorset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/Report/icon_car.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/Report/icon_hammer.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/Report/icon_light.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/Report/icon_water.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilColor.swift(2 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift(5 hunks)
✅ Files skipped from review due to trivial changes (4)
- Projects/Presentation/Resources/Colors.xcassets/Green/Green300.colorset/Contents.json
- Projects/Presentation/Resources/Colors.xcassets/Blue/Contents.json
- Projects/Presentation/Resources/Colors.xcassets/Blue/Blue300.colorset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Report/icon_car.imageset/Contents.json
🚧 Files skipped from review as they are similar to previous changes (1)
- Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-30T03:56:18.617Z
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
Applied to files:
Projects/DataSource/Sources/Repository/LocationRepository.swift
🧬 Code graph analysis (6)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (2)
Projects/DataSource/Sources/Repository/LocationRepository.swift (2)
fetchCoordinate(24-37)fetchAddress(39-47)Projects/DataSource/Sources/Repository/ReportRepository.swift (1)
report(11-13)
Projects/Domain/Sources/Protocol/Repository/LocationRepositoryProtocol.swift (1)
Projects/DataSource/Sources/Repository/LocationRepository.swift (2)
fetchCoordinate(24-37)fetchAddress(39-47)
Projects/Domain/Sources/Protocol/UseCase/ReportUseCaseProtocol.swift (1)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (2)
fetchCurrentLocation(17-21)report(23-25)
Projects/DataSource/Sources/Repository/LocationRepository.swift (2)
Projects/DataSource/Sources/NetworkService/NetworkService.swift (1)
request(25-48)Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift (1)
toLocationEntity(131-138)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (1)
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (1)
fetchCurrentLocation(17-21)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift
[Warning] 42-42: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🔇 Additional comments (22)
Projects/Presentation/Resources/Images.xcassets/Report/icon_hammer.imageset/Contents.json (1)
1-23: LGTM!표준 Xcode 에셋 카탈로그 형식을 올바르게 따르고 있으며, 1x/2x/3x 스케일 변형이 모두 정의되어 있습니다.
Projects/Presentation/Resources/Images.xcassets/Report/icon_light.imageset/Contents.json (1)
1-23: LGTM!표준 Xcode 에셋 카탈로그 형식을 올바르게 따르고 있으며, 모든 스케일 변형이 적절히 정의되어 있습니다.
Projects/Presentation/Sources/Common/DesignSystem/BitnagilColor.swift (2)
62-62: LGTM!기존 패턴을 일관되게 따라 green300 색상 상수가 올바르게 추가되었습니다.
105-107: LGTM!Blue Colors 섹션과 blue300 색상 상수가 기존 구조와 일관되게 추가되었습니다.
Projects/Presentation/Resources/Images.xcassets/Report/icon_water.imageset/Contents.json (1)
1-23: LGTM!표준 Xcode 에셋 카탈로그 형식을 올바르게 따르고 있으며, 모든 스케일 변형이 적절히 정의되어 있습니다.
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)
79-82: LGTM!제보 카테고리 UI를 위한 4개의 아이콘 상수가 기존 패턴을 일관되게 따라 올바르게 추가되었습니다. 에셋 이름이 카탈로그와 일치합니다.
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
66-73: LGTM!ReportUseCase의 의존성 주입 등록이 올바르게 구현되었습니다. LocationRepositoryProtocol과 ReportRepositoryProtocol을 모두 resolve하고, 없을 경우 명확한 에러 메시지와 함께 실패하도록 되어 있습니다.
Projects/Domain/Sources/Protocol/UseCase/ReportUseCaseProtocol.swift (1)
8-12: LGTM!프로토콜 정의가 명확하고 적절합니다. 비동기 메서드 시그니처도 올바르게 선언되었습니다.
Projects/Domain/Sources/UseCase/Report/ReportUseCase.swift (2)
8-15: LGTM!의존성 주입을 통한 초기화가 올바르게 구현되었습니다. 생성자에서 필요한 리포지토리들을 주입받고 있습니다.
17-21: LGTM!위치 정보 조회 로직이 명확합니다. 먼저 좌표를 가져오고, 좌표가 있을 때만 주소를 조회하는 흐름이 적절합니다.
Projects/Domain/Sources/Protocol/Repository/LocationRepositoryProtocol.swift (1)
8-12: LGTM!위치 리포지토리 프로토콜이 명확하게 정의되었습니다. 좌표 조회와 주소 변환 기능을 분리한 설계가 적절합니다.
Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift (3)
34-34: LGTM!ReportUseCase 의존성 주입이 올바르게 추가되었습니다.
45-46: LGTM!생성자를 통한 의존성 주입이 적절하게 구현되었습니다.
49-49: LGTM!카테고리 퍼블리셔가
ReportType.name을 사용하도록 변경되어 과거 리뷰 피드백이 반영되었습니다.Projects/DataSource/Sources/Repository/LocationRepository.swift (4)
17-22: LGTM!CLLocationManager 초기화 및 설정이 적절합니다. delegate 설정과 정확도 지정이 올바르게 되어 있습니다.
24-37: LGTM!좌표 조회 로직이 적절합니다. 위치 서비스 활성화 여부와 권한 상태를 확인한 후 continuation을 통해 비동기적으로 좌표를 가져오는 흐름이 올바릅니다.
39-47: LGTM!Kakao API를 통한 역지오코딩 로직이 명확합니다. 좌표를 기반으로 주소를 조회하고 fallback 좌표를 함께 전달하는 구조가 적절합니다.
69-81: LGTM!위치 업데이트 및 에러 처리 델리게이트 메서드가 올바르게 구현되었습니다. continuation을 적절히 재개하고 정리하고 있습니다.
Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift (4)
11-26: LGTM!Kakao API 응답의 메타데이터 구조가 올바르게 정의되었습니다. CodingKeys를 통한 snake_case 매핑도 적절합니다.
29-59: LGTM!Document 구조의 computed property를 통한 좌표 fallback 체인이 잘 설계되었습니다. y/x → address → roadAddress 순서로 값을 찾는 로직이 적절합니다.
62-92: LGTM!도로명 주소 구조가 명확하게 정의되었습니다. 모든 필드에 대한 CodingKeys 매핑과 computed property가 올바릅니다.
95-127: LGTM!지번 주소 구조가 명확하게 정의되었습니다. CodingKeys 매핑과 computed property가 적절합니다.
| func toLocationEntity(fallbackLongitude: Double? = nil, fallbackLatitude: Double? = nil) -> LocationEntity? { | ||
| guard let doc = documents.first else { return nil } | ||
| let longitude = doc.longitude ?? fallbackLongitude | ||
| let latitude = doc.latitude ?? fallbackLatitude | ||
| let address = doc.roadAddress?.addressName ?? doc.address?.addressName | ||
|
|
||
| return LocationEntity(longitude: longitude ?? 0, latitude: latitude ?? 0, address: address) | ||
| } |
There was a problem hiding this comment.
좌표가 nil일 때 (0, 0) 반환을 재고하세요.
137번째 줄에서 longitude ?? 0와 latitude ?? 0를 사용하면 좌표를 가져오지 못했을 때 (0, 0)이 반환됩니다. 이는 기니만(Gulf of Guinea)의 실제 위치이므로 잘못된 좌표로 오해될 수 있습니다.
좌표가 없는 경우 nil을 반환하거나, LocationEntity를 Optional로 만드는 것을 고려하세요.
다음 중 하나를 선택하세요:
옵션 1: nil 반환 (권장)
func toLocationEntity(fallbackLongitude: Double? = nil, fallbackLatitude: Double? = nil) -> LocationEntity? {
guard let doc = documents.first else { return nil }
let longitude = doc.longitude ?? fallbackLongitude
let latitude = doc.latitude ?? fallbackLatitude
+
+ guard let longitude = longitude, let latitude = latitude else { return nil }
let address = doc.roadAddress?.addressName ?? doc.address?.addressName
- return LocationEntity(longitude: longitude ?? 0, latitude: latitude ?? 0, address: address)
+ return LocationEntity(longitude: longitude, latitude: latitude, address: address)
}옵션 2: LocationEntity의 좌표를 Optional로 변경
(이 경우 Domain 계층의 LocationEntity 수정 필요)
🤖 Prompt for AI Agents
Projects/DataSource/Sources/DTO/KakaoLocationResponseDTO.swift lines 131-138:
the current implementation substitutes missing longitude/latitude with 0 which
yields a misleading (0,0) location; instead either return nil when coordinates
are missing (recommended) or propagate optional coordinates into LocationEntity
(alternative). Implement the recommended fix by checking that doc.longitude and
doc.latitude (or the fallbacks) are non-nil before constructing a LocationEntity
and return nil if either is missing; if you opt for the alternative, update
LocationEntity to accept Optional<Double> for longitude/latitude and adjust all
call sites and domain types accordingly so they handle optional coordinates.
| @@ -0,0 +1,12 @@ | |||
| // | |||
| // ReportUseCaserProtocol.swift | |||
There was a problem hiding this comment.
파일명 주석의 오타를 수정하세요.
주석에 파일명이 ReportUseCaserProtocol.swift로 되어 있는데, 실제 파일명은 ReportUseCaseProtocol.swift이어야 합니다 ("Caser"가 아닌 "CaseProtocol").
-// ReportUseCaserProtocol.swift
+// ReportUseCaseProtocol.swift📝 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.
| // ReportUseCaserProtocol.swift | |
| // ReportUseCaseProtocol.swift |
🤖 Prompt for AI Agents
In Projects/Domain/Sources/Protocol/UseCase/ReportUseCaseProtocol.swift around
line 2, the file header comment contains a typo: "ReportUseCaserProtocol.swift".
Replace that comment with the correct filename "ReportUseCaseProtocol.swift" so
the header matches the actual file name.
| Task { | ||
| do { | ||
| self.location = try await reportUseCase.fetchCurrentLocation() | ||
| } catch { | ||
|
|
||
| } | ||
|
|
||
| locationSubject.send(location?.address) | ||
| } | ||
| } |
There was a problem hiding this comment.
빈 catch 블록에서 에러를 처리하세요.
위치 정보 조회 중 발생한 에러가 무시되고 있습니다. 사용자에게 에러를 알리거나 최소한 로그를 남겨야 합니다.
다음과 같이 에러를 처리하세요:
private func configureLocation() {
Task {
do {
self.location = try await reportUseCase.fetchCurrentLocation()
} catch {
-
+ BitnagilLogger.log(logType: .error, message: "위치 정보 조회 실패: \(error)")
+ exceptionSubject.send("위치 정보를 가져올 수 없습니다.")
}
locationSubject.send(location?.address)
}
}🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Report/ViewModel/ReportViewModel.swift around
lines 91–100, the catch block for fetchCurrentLocation() is empty and silently
swallows errors; log the error (e.g., print or os_log) and publish it so the UI
can react (e.g., send an Error or user-facing message via an existing
errorSubject / @Published error property or create one if missing), ensure
locationSubject.send(nil) or location?.address is still sent after handling, and
keep the Task structure intact.
|
@choijungp 코드 리뷰 반영 + 색상 asset 추가 해두었습니다! 감사합니다! |
🌁 Background
📱 Screenshot
카테고리 선택
주소 불러오기
👩💻 Contents
📝 Review Note
역지오코딩
UseCase에서 좌표를 불러오고, 해당 좌표를 카카오Local API를 이용해서 주소로 변환해 받도록 구현했습니다.
LocationReposiotry에서 CoreLocation 을 이용해 사용자의 좌표 정보를 불러온 후, 해당 좌표를 기반으로 주소를 가져오는 api를 실행합니다.
아래 코드와 같이 Continuation을 이용해서 비동기적으로 실행되는 delegate 메서드를 처리하도록 했습니다.
정책상의 이유로 카카오API가 아니라 다른 서비스의 API를 사용할 수도 있어 위 과정을 분리했습니다
NetworkService의 performRequest 변경
📣 Related Issue
📬 Reference
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
디자인