Conversation
📝 WalkthroughWalkthrough캘린더 관련 엔드포인트·DTO·리포지토리·도메인 엔티티·유스케이스·뷰모델·뷰 및 DI를 async/throws 기반으로 재구성하고, 날짜 유틸·에셋·프로젝트 설정 참조 및 일부 프리뷰를 정리했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as CalendarView
participant VM as CalendarViewModel
participant UseCase as FetchTodayProcedureListUseCase
participant Repo as CalendarRepository
participant API as CalendarAPI
participant DTO as DTO Mapper
UI->>VM: 요청: fetchTodayProcedureList(date)
VM->>UseCase: execute(date)
UseCase->>Repo: fetchTodayProcedureList(date)
Repo->>API: CalendarAPI.daily(userID, date)
API-->>Repo: CalendarDailyResponseDTO
Repo->>DTO: response.toEntity()
DTO-->>Repo: [DailyProcedureEntity]
Repo-->>UseCase: [DailyProcedureEntity]
UseCase-->>VM: [DailyProcedureEntity]
VM-->>UI: 갱신: procedureList, treatmentDate
sequenceDiagram
participant UI as CalendarView
participant VM as CalendarViewModel
participant UseCase as FetchProcedureDowntimeUseCase
participant Repo as CalendarRepository
participant API as CalendarAPI
participant DTO as DTO Mapper
UI->>VM: 사용자 액션: 선택된 프로시저 tap -> fetchDowntimeByDay(procedureId)
VM->>UseCase: execute(procedureId)
UseCase->>Repo: fetchProcedureDowntime(id)
Repo->>API: CalendarAPI.downtime(userID, id)
API-->>Repo: CalendarDowntimeResponseDTO
Repo->>DTO: response.toEntity()
DTO-->>Repo: ProcedureDowntimeEntity
Repo-->>UseCase: ProcedureDowntimeEntity
UseCase-->>VM: ProcedureDowntimeEntity
VM-->>UI: 갱신: selectedDowntime, downtimeByDay
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Cherrish-iOS/Cherrish-iOS/Domain/Model/ProcedureDowntimeEntity.swift (1)
10-17: 엔티티 구조 적절함
ProcedureDowntimeEntity로 이름 변경하여 역할이 명확해졌고,Hashable준수로 SwiftUI 리스트에서 활용하기 좋습니다.
recoveryTargetDate가String타입인데, 도메인 레이어에서는Date타입을 사용하고 변환 로직을 DTO 레이어에 두는 것이 타입 안전성 측면에서 더 나을 수 있습니다. 현재 구조로도 동작에는 문제없으나, 향후 날짜 비교/연산이 필요할 경우 고려해 보세요.Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift (1)
20-46: 캘린더 DI 등록이 다른 리포지토리 실패에 의해 스킵될 수 있습니다.
HomeInterface나TreatmentInterface가 미등록이면 조립이 조기 반환되어 캘린더 유스케이스까지 등록되지 않습니다. 해당 의존성이 항상 보장되는지 확인하고, 필요하면 등록을 분리해 주세요.🔧 개선 예시
- guard let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else { - return - } - - guard let treatmentCategoryRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else { - return - } + let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) + let treatmentCategoryRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) @@ - DIContainer.shared.register(type: FetchDashboardData.self) { - return DefaultFetchDashboardData(repository: homeRepository) - } + if let homeRepository { + DIContainer.shared.register(type: FetchDashboardData.self) { + return DefaultFetchDashboardData(repository: homeRepository) + } + } @@ - DIContainer.shared.register(type: FetchTreatmentCategoriesUseCase.self) { - return DefaultFetchTreatmentCategoriesUseCase(repository: treatmentCategoryRepository) - } + if let treatmentCategoryRepository { + DIContainer.shared.register(type: FetchTreatmentCategoriesUseCase.self) { + return DefaultFetchTreatmentCategoriesUseCase(repository: treatmentCategoryRepository) + } + }
🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj`:
- Around line 275-276: 프로젝트가 참조하는 Core/Config.xcconfig가 리포지토리에 없어 빌드가 실패하므로,
project.pbxproj에서 사용된 baseConfigurationReferenceRelativePath (값:
Core/Config.xcconfig) 및 관련 baseConfigurationReferenceAnchor 항목들을 찾아 해당 xcconfig
파일을 실제로 추가하거나(파일명: Config.xcconfig 위치: Cherrish-iOS/Core) 참조를 제거/올바른 경로로
업데이트하세요; 관련 식별자: baseConfigurationReferenceRelativePath,
baseConfigurationReferenceAnchor, Config.xcconfig,
Cherrish-iOS.xcodeproj/project.pbxproj.
In `@Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift`:
- Around line 19-25: The DIContainer.shared.register closures in
DataDependencyAssembler capture self strongly causing retain cycles; update each
register call (for CalendarInterface, HomeInterface, TreatmentInterface) to
capture self weakly (e.g., use { [weak self] in guard let self = self else {
return nil as <Interface>? } ... } ) and return the corresponding repository
(e.g., DefaultCalendarRepository using self.networkService and
self.userDefaultService) so the assembler is not strongly retained by the
DIContainer; alternatively use [unowned self] if assembler truly lives for the
app lifetime.
In
`@Cherrish-iOS/Cherrish-iOS/Data/Model/Calendar/CalendarDowntimeResponseDTO.swift`:
- Around line 10-18: The DTO declares scheduledAt in CalendarDowntimeResponseDTO
but it is not mapped in toEntity(); either map it into ProcedureDowntimeEntity
or explicitly document why it's ignored. Fix by updating ProcedureDowntimeEntity
to include a scheduledAt property (matching type String or Date as used
elsewhere) and add the mapping inside CalendarDowntimeResponseDTO.toEntity() to
set entity.scheduledAt = scheduledAt, or if omission is intentional add a clear
comment above the scheduledAt declaration and/or a brief TODO in toEntity()
noting why it is omitted.
In
`@Cherrish-iOS/Cherrish-iOS/Data/Model/Calendar/CalendarMonthlyResponseDTO.swift`:
- Around line 14-21: In CalendarMonthlyResponseDTO.toEntity() the Int(entry.key)
conversion silently skips non-numeric keys; update this function to log a
warning when Int(entry.key) fails so malformed API keys are detectable — inside
the reduce over dailyProcedureCounts, when guard let key = Int(entry.key) fails,
call your app logging facility (e.g., Logger, os_log, or a provided process
logger) with context including the raw entry.key and entry.value before
continuing; ensure MonthlyEntity initialization remains unchanged and only the
conversion branch adds the warning.
In `@Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/CalendarAPI.swift`:
- Around line 51-56: The parameterEncoding computed property in CalendarAPI
currently returns JSONEncoding() for the .monthly, .daily, and .downtime cases
which sends params in the HTTP body (wrong for GET); change it to return
URLEncoding.queryString so query parameters are encoded in the URL for these GET
endpoints (update the parameterEncoding var in the CalendarAPI type and ensure
the switch cases .monthly, .daily, .downtime return URLEncoding.queryString).
In `@Cherrish-iOS/Cherrish-iOS/Data/Repository/CalendarRepository.swift`:
- Around line 14-19: The initializer in CalendarRepository is ignoring the
injected dependencies by always instantiating DefaultNetworkService and
DefaultUserDefaultService; change the init to assign the passed parameters to
the properties (self.networkService = networkService, self.userDefaultService =
userDefaultService) so DI works correctly, or if you want default fallbacks
provide default parameter values in the init signature (e.g., networkService:
NetworkService = DefaultNetworkService()) and still assign the parameters to the
properties; update the initializer that currently references
DefaultNetworkService and DefaultUserDefaultService accordingly.
- Around line 64-90: The two mock methods do not match the protocol signature;
update fetchProcedureCountOfMonth and fetchTodayProcedureList in
MockCalendarRepository to have the same async throws signature as the
CalendarInterface (i.e., declare them as async throws and keep their current
return types MonthlyEntity and [DailyProcedureEntity]) and ensure their
implementations either return the mock data or throw appropriate errors if
needed so the signatures compile against the protocol.
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/DailyProcedureEntity.swift`:
- Around line 10-15: DailyProcedureEntity의 type 프로퍼티가 현재 String으로 되어 있는데 시술 유형이
정해진 값들이면 enum으로 변경해 타입 안전성 및 자동완성을 얻을 수 있습니다; DailyProcedureEntity의 type을
String에서 적절한 enum 타입(예: ProcedureType)으로 대체하고, 필요한 경우 Codable/Hashable 채택과
rawValue 기반 초기화 또는 API 응답을 안전하게 처리할 수 있도록 init(from:) 변환 로직(또는 failable
initializer)을 추가해 예측 불가능한 값도 처리하도록 구현하세요.
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/MonthlyEntity.swift`:
- Around line 10-12: MonthlyEntity.dailyProcedureCounts uses Int keys that
represent the calendar day but lacks documentation; add a brief doc comment
above the MonthlyEntity struct and the dailyProcedureCounts property clarifying
that the dictionary key is the day-of-month (1...31) and what the value
represents (procedure count), or introduce a small typealias (e.g., typealias
Day = Int) and change the property signature to dailyProcedureCounts: [Day: Int]
with the same doc comment to make intent explicit; update any initializer/usage
to use the Day alias if you add it.
In `@Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchProcedureCountOfMonth.swift`:
- Around line 10-12: Rename the protocol FetchProcedureCountOfMonth to
FetchProcedureCountOfMonthUseCase for naming consistency; update the protocol
declaration and all references/usages (e.g., any variables, dependency
injections, type annotations, and conforming types) to the new name so
FetchProcedureCountOfMonthUseCase replaces FetchProcedureCountOfMonth across the
codebase, and ensure any implementations (conforming structs/classes) update
their protocol conformance accordingly.
In
`@Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchTodayProcedureListUseCase.swift`:
- Around line 10-14: Rename the implementation to match the protocol and file:
change struct DefaultFetchTodayProcedureUseCase to
DefaultFetchTodayProcedureListUseCase and rename the file to
FetchTodayProcedureListUseCase.swift; update all references/usages
(initializers, type annotations, imports, tests) to the new struct name so
DefaultFetchTodayProcedureListUseCase conforms to
FetchTodayProcedureListUseCase.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarCellView.swift`:
- Line 26: The isDDay property on CalendarCellView should be made immutable:
change the declaration from var isDDay: Bool to let isDDay: Bool in the
CalendarCellView struct/class (ensure any initializer assigns it), remove any
internal assignments to isDDay, and update any external call sites that relied
on mutating it to instead create a new instance or pass the value via the
initializer; verify there are no places setting isDDay after init (search for
"isDDay" usages) and update accordingly.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`:
- Around line 50-58: The current .task(id: viewModel.currentMonth) block guards
execution with `if calendarMode == .none`, preventing
`fetchProcedureCountsOfMonth()` from running when the month changes while in
`.selectedProcedure` mode; remove that guard so that inside the `.task(id:
viewModel.currentMonth)` you always call `try await
viewModel.fetchProcedureCountsOfMonth()` (and still call `try await
viewModel.fetchTodayProcedureList()` as needed), keeping the existing do/catch
and the `CherrishLogger.error(error)` handling so month changes always trigger
data refresh regardless of `calendarMode`.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`:
- Around line 123-126: getCurrentMonth creates a gregorian Calendar fixed to
TimeZone(identifier: "Asia/Seoul") while other helpers (getYearAndMonthString,
extractDate, firstDateOfCurrentMonth) use Calendar.current, which can cause
inconsistent month/day calculations on devices in different time zones; update
the code to use a single shared Calendar instance (e.g., create a private var
kstCalendar = Calendar(identifier: .gregorian) with timeZone =
TimeZone(identifier: "Asia/Seoul") and replace usages of Calendar.current in
getYearAndMonthString, extractDate, firstDateOfCurrentMonth (and any other date
helpers) to use kstCalendar or pass the calendar as a parameter so all date math
uses the same calendar/timeZone consistently.
- Around line 110-112: Do not set treatmentDate before fetching — fetch the
procedures first and only update state on success: call
fetchTodayProcedureListUseCase.execute(date: selectedDate.toDateString()) into a
local/temp variable, then on success assign procedureList = fetchedValue and
treatmentDate = selectedDate.toDateString(); on failure leave
treatmentDate/procedureList unchanged and handle/log the error; update the code
around treatmentDate, procedureList and fetchTodayProcedureListUseCase.execute
accordingly.
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date`+.swift:
- Around line 25-32: The static DateFormatter named dateFormatter in extension
Date creates ambiguity with the similarly named dateFormatter() in
String+.swift; please rename the symbol (e.g., isoDateFormatter or
koreanDateFormatter) to a more specific identifier and update all usages to the
new name (reference: Date.dateFormatter) so callers no longer collide or get
confused with String+.dateFormatter().
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/String`+.swift:
- Around line 33-42: The toKoreanMonthDay computed property should reuse static
DateFormatters with explicit locale and timeZone to avoid per-call allocation
and day-shift bugs: create two static formatters (e.g., isoFormatter and
koreanFormatter) where isoFormatter is configured with dateFormat "yyyy-MM-dd",
locale = Locale(identifier: "en_US_POSIX") and timeZone =
TimeZone(secondsFromGMT: 0) for stable parsing, and koreanFormatter is
configured with dateFormat "M월 d일", locale = Locale(identifier: "ko_KR") and an
appropriate timeZone (e.g., TimeZone.current); then replace the local
DateFormatter in String.toKoreanMonthDay to parse with isoFormatter and format
with koreanFormatter.
- Around line 11-15: The inputFormatter DateFormatter uses a locale of "ko_KR"
but fixed-format parsing must use Locale(identifier: "en_US_POSIX"); update the
static inputFormatter initializer (inputFormatter) to set dateFormatter.locale =
Locale(identifier: "en_US_POSIX") and also set a stable calendar/timeZone (e.g.,
dateFormatter.calendar = Calendar(identifier: .gregorian) and
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)) to ensure deterministic
parsing of "yyyy-MM-dd".
| baseConfigurationReferenceAnchor = 51B5D31A2F0514390090B8B4 /* Cherrish-iOS */; | ||
| baseConfigurationReferenceRelativePath = Core/Config.xcconfig; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1) Search for Config.xcconfig in the repository
echo "=== Searching for Config.xcconfig ==="
fd -a 'Config\.xcconfig$' 2>/dev/null || echo "No Config.xcconfig found"
# 2) Check the expected path
echo ""
echo "=== Checking expected path ==="
if [ -f "Cherrish-iOS/Core/Config.xcconfig" ]; then
echo "✓ File exists at Cherrish-iOS/Core/Config.xcconfig"
wc -l "Cherrish-iOS/Core/Config.xcconfig"
else
echo "✗ File NOT found at Cherrish-iOS/Core/Config.xcconfig"
fi
# 3) List contents of Cherrish-iOS/Core directory if it exists
echo ""
echo "=== Contents of Cherrish-iOS/Core ==="
if [ -d "Cherrish-iOS/Core" ]; then
ls -la "Cherrish-iOS/Core/" 2>/dev/null || echo "Directory exists but cannot list contents"
else
echo "Directory Cherrish-iOS/Core does not exist"
fi
# 4) Check pbxproj references to Config.xcconfig
echo ""
echo "=== References to Config.xcconfig in pbxproj ==="
rg -n "Config\.xcconfig" "Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj" 2>/dev/null || echo "No references found"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 605
Config.xcconfig 파일이 누락되어 빌드 실패
Core/Config.xcconfig 파일이 리포지토리에 존재하지 않습니다. Cherrish-iOS/Core 디렉토리 자체도 없으며, 프로젝트 파일에서는 4개 라인(153, 218, 276, 314)에서 이 파일을 참조하고 있습니다. 이 설정 파일을 생성하거나 참조를 제거해야 빌드가 가능합니다.
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj` around lines 275 - 276,
프로젝트가 참조하는 Core/Config.xcconfig가 리포지토리에 없어 빌드가 실패하므로, project.pbxproj에서 사용된
baseConfigurationReferenceRelativePath (값: Core/Config.xcconfig) 및 관련
baseConfigurationReferenceAnchor 항목들을 찾아 해당 xcconfig 파일을 실제로 추가하거나(파일명:
Config.xcconfig 위치: Cherrish-iOS/Core) 참조를 제거/올바른 경로로 업데이트하세요; 관련 식별자:
baseConfigurationReferenceRelativePath, baseConfigurationReferenceAnchor,
Config.xcconfig, Cherrish-iOS.xcodeproj/project.pbxproj.
| func assemble() { | ||
| DIContainer.shared.register(type: CalendarInterface.self) { | ||
| return MockCalendarRepository() | ||
| return DefaultCalendarRepository( | ||
| networkService: self.networkService, | ||
| userDefaultService: self.userDefaultService | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for DIContainer class definition and implementation
fd -e swift | head -20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1319
🏁 Script executed:
#!/bin/bash
# Find DIContainer definition
rg -n "class DIContainer" --type swift -A 20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1700
🏁 Script executed:
#!/bin/bash
# Find DataDependencyAssembler usage patterns
rg -n "DataDependencyAssembler" --type swift -B 2 -A 5Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2193
🏁 Script executed:
#!/bin/bash
# Check the register method signature and other uses
rg -n "\.register\(" --type swift -B 1 -A 3 | head -100Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 7271
🏁 Script executed:
#!/bin/bash
# Get full DataDependencyAssembler.swift
cat -n Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1302
🏁 Script executed:
#!/bin/bash
# Check the app initialization to confirm DataDependencyAssembler is only used during startup
cat -n Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 538
DIContainer 클로저에서 self 강한 참조로 인한 메모리 누수 발생
DIContainer.shared.register() 호출 시 클로저가 self를 강하게 캡처하고 있습니다. DIContainer는 싱글톤이고 dependencies 딕셔너리가 앱 전체 생명주기 동안 유지되므로, 각 클로저는 DataDependencyAssembler 인스턴스에 대한 강한 참조를 계속 유지하게 됩니다. 이로 인해 retain cycle이 발생하여 assembler가 메모리에서 해제되지 않습니다.
모든 register() 호출(CalendarInterface, HomeInterface, TreatmentInterface)을 다음과 같이 수정하세요:
DIContainer.shared.register(type: CalendarInterface.self) { [weak self] in
guard let self = self else { return nil as CalendarInterface? }
return DefaultCalendarRepository(
networkService: self.networkService,
userDefaultService: self.userDefaultService
)
}또는 assembler의 생명주기가 app 시작 이후 필요 없다면 [unowned self] 사용도 고려할 수 있습니다.
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift` around lines 19
- 25, The DIContainer.shared.register closures in DataDependencyAssembler
capture self strongly causing retain cycles; update each register call (for
CalendarInterface, HomeInterface, TreatmentInterface) to capture self weakly
(e.g., use { [weak self] in guard let self = self else { return nil as
<Interface>? } ... } ) and return the corresponding repository (e.g.,
DefaultCalendarRepository using self.networkService and self.userDefaultService)
so the assembler is not strongly retained by the DIContainer; alternatively use
[unowned self] if assembler truly lives for the app lifetime.
| struct CalendarDowntimeResponseDTO: Decodable { | ||
| let userProcedureId: Int | ||
| let scheduledAt: String | ||
| let downtimeDays: Int | ||
| let recoveryTargetDate: String | ||
| let sensitiveDays: [String] | ||
| let cautionDays: [String] | ||
| let recoveryDays: [String] | ||
| } |
There was a problem hiding this comment.
scheduledAt 필드가 사용되지 않음
scheduledAt 프로퍼티(Line 12)가 선언되어 있지만 toEntity() 매핑에서 사용되지 않습니다. 의도적으로 무시하는 것이라면 주석을 추가하거나, 필요한 경우 ProcedureDowntimeEntity에 해당 필드를 추가해 주세요.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Data/Model/Calendar/CalendarDowntimeResponseDTO.swift`
around lines 10 - 18, The DTO declares scheduledAt in
CalendarDowntimeResponseDTO but it is not mapped in toEntity(); either map it
into ProcedureDowntimeEntity or explicitly document why it's ignored. Fix by
updating ProcedureDowntimeEntity to include a scheduledAt property (matching
type String or Date as used elsewhere) and add the mapping inside
CalendarDowntimeResponseDTO.toEntity() to set entity.scheduledAt = scheduledAt,
or if omission is intentional add a clear comment above the scheduledAt
declaration and/or a brief TODO in toEntity() noting why it is omitted.
| extension CalendarMonthlyResponseDTO { | ||
| func toEntity() -> MonthlyEntity { | ||
| let mapped = dailyProcedureCounts.reduce(into: [Int: Int]()) { result, entry in | ||
| guard let key = Int(entry.key) else { return } | ||
| result[key] = entry.value | ||
| } | ||
| return .init(dailyProcedureCounts: mapped) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
숫자가 아닌 키 변환 실패 시 디버깅을 위한 로깅을 고려하세요.
현재 구현은 Int(entry.key)가 실패할 경우 해당 엔트리를 무시합니다. API 응답 데이터 문제를 조기에 발견하기 위해 경고 로그를 추가하는 것을 권장합니다.
♻️ 제안된 수정
func toEntity() -> MonthlyEntity {
let mapped = dailyProcedureCounts.reduce(into: [Int: Int]()) { result, entry in
- guard let key = Int(entry.key) else { return }
+ guard let key = Int(entry.key) else {
+ CherrishLogger.warning("Invalid day key in monthly response: \(entry.key)")
+ return
+ }
result[key] = entry.value
}
return .init(dailyProcedureCounts: mapped)
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Data/Model/Calendar/CalendarMonthlyResponseDTO.swift`
around lines 14 - 21, In CalendarMonthlyResponseDTO.toEntity() the
Int(entry.key) conversion silently skips non-numeric keys; update this function
to log a warning when Int(entry.key) fails so malformed API keys are detectable
— inside the reduce over dailyProcedureCounts, when guard let key =
Int(entry.key) fails, call your app logging facility (e.g., Logger, os_log, or a
provided process logger) with context including the raw entry.key and
entry.value before continuing; ensure MonthlyEntity initialization remains
unchanged and only the conversion branch adds the warning.
| treatmentDate = selectedDate.toDateString() | ||
| procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate) | ||
| } |
There was a problem hiding this comment.
실패 시 상태 불일치 가능—업데이트 순서 조정 필요.
Line 110에서 날짜를 먼저 갱신하면, fetch 실패 시 treatmentDate만 바뀌고 procedureList는 이전 값으로 남습니다. 성공 후에 함께 업데이트하는 편이 안전합니다.
🔧 제안 수정안
`@MainActor`
func fetchTodayProcedureList() async throws {
- treatmentDate = selectedDate.toDateString()
- procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate)
+ let dateString = selectedDate.toDateString()
+ let list = try await fetchTodayProcedureListUseCase.execute(date: dateString)
+ treatmentDate = dateString
+ procedureList = list
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 110 - 112, Do not set treatmentDate before fetching — fetch the
procedures first and only update state on success: call
fetchTodayProcedureListUseCase.execute(date: selectedDate.toDateString()) into a
local/temp variable, then on success assign procedureList = fetchedValue and
treatmentDate = selectedDate.toDateString(); on failure leave
treatmentDate/procedureList unchanged and handle/log the error; update the code
around treatmentDate, procedureList and fetchTodayProcedureListUseCase.execute
accordingly.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift
Show resolved
Hide resolved
| private static let inputFormatter: DateFormatter = { | ||
| let dateFormatter = DateFormatter() | ||
| dateFormatter.dateFormat = "yyyy-MM-dd" | ||
| dateFormatter.locale = Locale(identifier: "en_US_POSIX") | ||
| dateFormatter.locale = Locale(identifier: "ko_KR") | ||
| return dateFormatter |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "String+.swift" -o -name "*String*.swift" | head -20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 141
🏁 Script executed:
cd Cherrish-iOS && find . -path "*Presentation/Global/Extension*" -type fRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 812
🏁 Script executed:
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/String+.swift" | head -60Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1536
🏁 Script executed:
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date+.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1881
🌐 Web query:
iOS DateFormatter locale en_US_POSIX vs ko_KR yyyy-MM-dd parsing behavior
💡 Result:
Short answer
- en_US_POSIX with a fixed dateFormat (e.g. "yyyy-MM-dd") produces stable, predictable parsing (Gregorian calendar, no user overrides). Use it for machine/interop formats. [1][2]
- Using ko_KR (or other user/locales) can change the formatter’s calendar, era and format interpretation (and can be affected by user settings like 24‑hour/AM‑PM or regional calendar rules), which can make "yyyy-MM-dd" parse incorrectly or yield the wrong year/day. Set the locale to en_US_POSIX (and explicitly the gregorian calendar/timeZone) to avoid this. [1][2][3]
Why this happens (brief)
- Apple’s QA (QA1480) and docs: en_US_POSIX is the POSIX locale intended for fixed-format parsing; other locales may alter parsing behavior/format string interpretation. [1][2]
- Practical reports show wrong years/offsets when the locale/calendar is not fixed (e.g. device locale like ko_KR or modified en_US variants). For parsing you should set locale = Locale(identifier: "en_US_POSIX") and, if needed, formatter.calendar = Calendar(identifier: .gregorian) and formatter.timeZone = TimeZone(secondsFromGMT: 0). [3][1]
References
- Apple Technical Q&A QA1480 (NSDateFormatter and Internet Dates). [1]
- Apple Date Formatters docs / Data Formatting guide. [2]
- Examples / community reports (Stack Overflow showing wrong-year behavior and recommendation to use en_US_POSIX). [3]
🌐 Web query:
DateFormatter parsing fixed format yyyy-MM-dd locale impact calendar
💡 Result:
Short answer: don’t rely on the default locale/calendar. For a fixed-format string like "yyyy‑MM‑dd" set the formatter’s locale to en_US_POSIX and its calendar (usually Gregorian) — otherwise a user-chosen calendar (Buddhist, Hebrew, etc.) or locale preferences can change how the year/month/day are interpreted. Also set timeZone if you need a deterministic midnight instant. Example:
let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX") // fixes locale-related rewriting
f.calendar = Calendar(identifier: .gregorian) // avoids user-selected calendar math
f.timeZone = TimeZone(secondsFromGMT: 0) // optional — makes parsed Date deterministic
f.dateFormat = "yyyy-MM-dd"
let date = f.date(from: "2026-01-20")
Why: Apple’s docs and QA note explain that format strings are still affected by the user’s locale and calendar and recommend en_US_POSIX for fixed internet/formatted dates. [1][2]
Sources:
[1] Apple Technical Q&A QA1480 (NSDateFormatter and Internet Dates).
[2] Apple Date Formatters documentation (NSDateFormatter behavior and calendars).
고정 포맷 파싱은 반드시 en_US_POSIX를 사용해야 합니다.
Apple의 공식 권장사항(QA1480)에 따르면, "yyyy-MM-dd" 같은 고정 포맷 문자열 파싱은 en_US_POSIX 로케일을 반드시 사용해야 합니다. 현재 ko_KR 로케일은 사용자의 캘린더 설정(예: 불교력, 히브리력) 및 지역 규칙에 따라 연/월/일 해석이 변경되어 파싱 실패 또는 잘못된 날짜 변환을 초래할 수 있습니다.
🔧 제안 수정
private static let inputFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
- dateFormatter.locale = Locale(identifier: "ko_KR")
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX")
+ dateFormatter.calendar = Calendar(identifier: .gregorian)
return dateFormatter
}()📝 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.
| private static let inputFormatter: DateFormatter = { | |
| let dateFormatter = DateFormatter() | |
| dateFormatter.dateFormat = "yyyy-MM-dd" | |
| dateFormatter.locale = Locale(identifier: "en_US_POSIX") | |
| dateFormatter.locale = Locale(identifier: "ko_KR") | |
| return dateFormatter | |
| private static let inputFormatter: DateFormatter = { | |
| let dateFormatter = DateFormatter() | |
| dateFormatter.dateFormat = "yyyy-MM-dd" | |
| dateFormatter.locale = Locale(identifier: "en_US_POSIX") | |
| dateFormatter.calendar = Calendar(identifier: .gregorian) | |
| return dateFormatter | |
| }() |
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/String`+.swift around
lines 11 - 15, The inputFormatter DateFormatter uses a locale of "ko_KR" but
fixed-format parsing must use Locale(identifier: "en_US_POSIX"); update the
static inputFormatter initializer (inputFormatter) to set dateFormatter.locale =
Locale(identifier: "en_US_POSIX") and also set a stable calendar/timeZone (e.g.,
dateFormatter.calendar = Calendar(identifier: .gregorian) and
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)) to ensure deterministic
parsing of "yyyy-MM-dd".
| var toKoreanMonthDay: String { | ||
| let formatter = DateFormatter() | ||
| formatter.dateFormat = "yyyy-MM-dd" | ||
|
|
||
| guard let parsedDate = formatter.date(from: self) else { | ||
| return self | ||
| } | ||
|
|
||
| formatter.dateFormat = "M월 d일" | ||
| return formatter.string(from: parsedDate) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
toKoreanMonthDay에서 포맷터 재사용 + 설정 일관성을 맞춰주세요.
매 호출마다 DateFormatter를 생성하고 locale/timeZone을 지정하지 않아 기존 공유 포맷터와 설정 불일치가 생길 수 있습니다(특히 날짜 하루 밀림 이슈 재발 가능). 정적 포맷터로 재사용하면서 설정을 일치시키는 편이 안전합니다.
♻️ 제안 리팩터
private static let outputFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "M월 d일 EEEE"
dateFormatter.locale = Locale(identifier: "ko_KR")
return dateFormatter
}()
+
+private static let monthDayFormatter: DateFormatter = {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "M월 d일"
+ dateFormatter.locale = Locale(identifier: "ko_KR")
+ return dateFormatter
+}()
@@
var toKoreanMonthDay: String {
- let formatter = DateFormatter()
- formatter.dateFormat = "yyyy-MM-dd"
-
- guard let parsedDate = formatter.date(from: self) else {
+ guard let parsedDate = String.inputFormatter.date(from: self) else {
return self
}
-
- formatter.dateFormat = "M월 d일"
- return formatter.string(from: parsedDate)
+ return String.monthDayFormatter.string(from: parsedDate)
}🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/String`+.swift around
lines 33 - 42, The toKoreanMonthDay computed property should reuse static
DateFormatters with explicit locale and timeZone to avoid per-call allocation
and day-shift bugs: create two static formatters (e.g., isoFormatter and
koreanFormatter) where isoFormatter is configured with dateFormat "yyyy-MM-dd",
locale = Locale(identifier: "en_US_POSIX") and timeZone =
TimeZone(secondsFromGMT: 0) for stable parsing, and koreanFormatter is
configured with dateFormat "M월 d일", locale = Locale(identifier: "ko_KR") and an
appropriate timeZone (e.g., TimeZone.current); then replace the local
DateFormatter in String.toKoreanMonthDay to parse with isoFormatter and format
with koreanFormatter.
0ca3cc5 to
2b4e6ec
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift (1)
40-42: 에러 처리의 일관성이 부족합니다.
FetchDashboardDataresolve 실패 시 로깅 없이 단순 return하고 있어, 위의 lines 28-29에서CherrishLogger.error(CherrishError.DIFailedError)를 호출하는 패턴과 일관성이 없습니다.수정 제안
guard let fetchDashboardData = DIContainer.shared.resolve(type: FetchDashboardData.self) else { + CherrishLogger.error(CherrishError.DIFailedError) return }
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`:
- Line 113: The call passing selectedProcedureID ?? 0 can accidentally treat 0
as a valid procedure ID; update the call to avoid a potentially valid fallback
by either passing the Optional directly (selectedProcedureID) to isDDay or using
an explicit invalid sentinel like -1 (e.g., selectedProcedureID ?? -1) and
ensure isDDay(_:selectedProcedureID:) handles an optional/invalid ID
accordingly; locate the call site and the isDDay function to make the
signature/handling consistent.
- Around line 177-189: The tap handler can start overlapping Tasks causing stale
data; add a cancellable Task property (e.g., a `@State` private var fetchTask) and
in the onTapGesture cancel any existing fetchTask before creating a new one,
then assign fetchTask = Task { ... } to run
viewModel.fetchDowntimeByDay(procedureId:), handle CancellationError separately,
and after awaiting verify selectedProcedureID (and/or compare
procedure.procedureId) before applying any result to selectedDowntime or other
UI state; this ensures previous fetches are cancelled and results are only
applied if still the current selection.
♻️ Duplicate comments (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift (1)
50-58: 월 변경 시 데이터 갱신이 반드시 필요합니다.현재 코드에서
calendarMode == .none가드로 인해 시술 상세 모드(.selectedProcedure)에서 월을 변경해도fetchProcedureCountsOfMonth()가 실행되지 않습니다. 달력 그리드가 각 날짜의 시술 개수를 표시해야 하므로 모드와 관계없이 월 변경 시 항상 데이터를 갱신해야 합니다.
| procedureCount: viewModel.getProcedureCount(for: value), | ||
| isSelected: viewModel.isSelected(value), | ||
| downtimeState: viewModel.getDowntimeState(for: value.date), | ||
| isDDay: viewModel.isDDay(for: value.date, selectedProcedureID: selectedProcedureID ?? 0), |
There was a problem hiding this comment.
selectedProcedureID ?? 0 사용 시 잠재적 문제가 있습니다.
selectedProcedureID가 nil일 때 fallback 값으로 0을 사용하고 있는데, 만약 0이 유효한 procedure ID라면 의도치 않게 D-Day가 표시될 수 있습니다. isDDay 함수 내부에서 guard 처리가 되어 있다면 문제없지만, 명시적으로 -1 같은 무효한 값을 사용하거나 Optional을 직접 전달하는 것이 더 안전합니다.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
at line 113, The call passing selectedProcedureID ?? 0 can accidentally treat 0
as a valid procedure ID; update the call to avoid a potentially valid fallback
by either passing the Optional directly (selectedProcedureID) to isDDay or using
an explicit invalid sentinel like -1 (e.g., selectedProcedureID ?? -1) and
ensure isDDay(_:selectedProcedureID:) handles an optional/invalid ID
accordingly; locate the call site and the isDDay function to make the
signature/handling consistent.
| .onTapGesture { | ||
| calendarMode.toggle() | ||
| selectedProcedureID = procedure.procedureId | ||
| viewModel.fetchDowntimeByDay(procedureId: procedure.procedureId) | ||
|
|
||
| Task { | ||
| do { | ||
| try await viewModel.fetchDowntimeByDay(procedureId: procedure.procedureId) | ||
| } | ||
| catch { | ||
| CherrishLogger.error(error) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
연속 탭 시 race condition 가능성이 있습니다.
사용자가 다른 procedure를 빠르게 연속으로 탭하면, 이전 fetchDowntimeByDay 요청이 완료되기 전에 새로운 요청이 시작될 수 있습니다. 이 경우 selectedDowntime이 현재 선택된 procedure와 맞지 않는 데이터로 업데이트될 수 있습니다.
수정 제안: Task 취소 처리 추가
+ `@State` private var currentDowntimeTask: Task<Void, Never>? = nil그리고 onTapGesture 내부에서:
.onTapGesture {
calendarMode.toggle()
selectedProcedureID = procedure.procedureId
+ currentDowntimeTask?.cancel()
- Task {
+ currentDowntimeTask = Task {
do {
try await viewModel.fetchDowntimeByDay(procedureId: procedure.procedureId)
- }
- catch {
+ } catch {
+ if Task.isCancelled { return }
CherrishLogger.error(error)
}
}
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
around lines 177 - 189, The tap handler can start overlapping Tasks causing
stale data; add a cancellable Task property (e.g., a `@State` private var
fetchTask) and in the onTapGesture cancel any existing fetchTask before creating
a new one, then assign fetchTask = Task { ... } to run
viewModel.fetchDowntimeByDay(procedureId:), handle CancellationError separately,
and after awaiting verify selectedProcedureID (and/or compare
procedure.procedureId) before applying any result to selectedDowntime or other
UI state; this ensures previous fetches are cancelled and results are only
applied if still the current selection.
d9b67d3 to
e31983c
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift`:
- Around line 24-30: Remove the redundant dependency resolution for
TreatmentInterface: delete the unused guard that assigns
treatmentCategoryRepository (the guard using DIContainer.shared.resolve(type:
TreatmentInterface.self) at the top) and rely on the existing resolution that
assigns treatmentRepository later (the resolve for TreatmentInterface in
treatmentRepository). Ensure only one DIContainer.shared.resolve(type:
TreatmentInterface.self) remains and that all uses reference treatmentRepository
so no unused variables remain.
- Around line 40-42: Remove the duplicate resolution of HomeInterface: delete
the second guard that calls DIContainer.shared.resolve(type: HomeInterface.self)
and instead reuse the earlier homeRepository obtained on lines 24-26 (the
originally resolved variable named homeRepository) in the code paths that
follow; ensure there are no shadowed variables with the same name and that any
subsequent references use the initial homeRepository instance rather than
resolving again.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`:
- Line 330: The closing brace in CalendarView.swift for the CalendarView
declaration/closure is indented with 2 spaces instead of the project's 4-space
style; locate the mismatched brace in the CalendarView (e.g., end of
struct/extension or body where the lone "}" appears) and re-indent that closing
brace to 4 spaces so it matches the rest of the file and project coding style.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`:
- Around line 78-91: isDDay currently creates a DateFormatter and Calendar on
every call which is costly; add cached static properties (e.g., private static
let kstDateFormatter: DateFormatter and private static let kstCalendar:
Calendar) inside CalendarViewModel configured with "yyyy-MM-dd",
Locale(identifier: "ko_KR") and TimeZone(identifier: "Asia/Seoul") and replace
the local formatter/calendar creation in isDDay(for:selectedProcedureID:) to use
Self.kstDateFormatter and Self.kstCalendar (avoid force-unwrapping
timeZone—provide a sensible fallback) while keeping the existing guard checks
for selectedDowntime and parsing the target date.
- Around line 86-87: Replace the forced unwrap of TimeZone(identifier:
"Asia/Seoul") when creating the Calendar with safe handling: obtain the timezone
using guard/if-let (e.g., guard let seoulTZ = TimeZone(identifier: "Asia/Seoul")
else { ... }) or provide a fallback (e.g., TimeZone.current) and use that value
to set calendar.timeZone; apply the same change in places referenced by isDDay
and getCurrentMonth so neither uses a forced unwrap of TimeZone(identifier:).
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date`+.swift:
- Around line 20-32: The getDowntimeState() method must be annotated with
`@MainActor` to ensure calls that use the shared Date.dateFormatter run on the
main thread; update the declaration of getDowntimeState() to include `@MainActor`
(same pattern used by fetchTodayProcedureList()) so the compiler enforces
main-thread execution whenever getDowntimeState() accesses Date.dateFormatter or
other UI/main-only resources.
♻️ Duplicate comments (17)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/String+.swift (2)
11-16: 고정 포맷 파싱에는en_US_POSIX로케일을 사용해야 합니다.이전 리뷰에서 지적된 사항이 아직 해결되지 않았습니다.
"yyyy-MM-dd"같은 고정 포맷 문자열 파싱 시ko_KR로케일을 사용하면 사용자 캘린더 설정(불교력, 히브리력 등)에 따라 날짜 해석이 달라질 수 있습니다. Apple QA1480 권장사항에 따라en_US_POSIX로 변경해 주세요.🔧 제안 수정
private static let inputFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" - dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.calendar = Calendar(identifier: .gregorian) return dateFormatter }()
33-43:toKoreanMonthDay에서 정적 포맷터를 재사용하고 로케일 설정을 추가해 주세요.이전 리뷰에서 지적된 사항입니다. 현재 구현은 매 호출마다
DateFormatter를 새로 생성하여 성능 저하를 유발하며, 로케일/타임존 미설정으로 인해 파싱 동작이 일관되지 않을 수 있습니다. 기존inputFormatter를 재사용하고 출력용 정적 포맷터를 추가하는 것이 좋습니다.♻️ 제안 리팩터
+private static let monthDayFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "M월 d일" + dateFormatter.locale = Locale(identifier: "ko_KR") + return dateFormatter +}() + var toKoreanMonthDay: String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - guard let parsedDate = formatter.date(from: self) else { + guard let parsedDate = String.inputFormatter.date(from: self) else { return self } - - formatter.dateFormat = "M월 d일" - return formatter.string(from: parsedDate) + return String.monthDayFormatter.string(from: parsedDate) }Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj (1)
275-276: Config.xcconfig 경로 유효성 확인 필요
Core/Config.xcconfig가 실제로 존재하지 않거나 경로가 다르면 타깃 Debug/Release 빌드가 바로 실패합니다. 해당 파일을 리포지토리에 추가하거나baseConfigurationReferenceRelativePath를 실제 위치로 맞춰 주세요.다음 스크립트로 파일 존재 여부와 pbxproj 참조를 확인해 주세요:
#!/bin/bash # Config.xcconfig 존재 여부 및 참조 확인 (read-only) echo "=== Config.xcconfig 위치 검색 ===" fd -a 'Config\.xcconfig$' 2>/dev/null || echo "Config.xcconfig 파일을 찾지 못했습니다." echo "" echo "=== pbxproj 내 참조 확인 ===" rg -n "baseConfigurationReferenceRelativePath = Core/Config\.xcconfig" \ Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxprojAlso applies to: 313-314
Cherrish-iOS/Cherrish-iOS/Domain/Model/MonthlyEntity.swift (1)
10-12: 간결하고 적절한 도메인 엔티티 구조입니다.월별 시술 횟수를 일별로 관리하는 심플한 구조로, 목적에 부합합니다.
문서화 관련 제안은 이전 리뷰에서 이미 언급되었습니다.
Cherrish-iOS/Cherrish-iOS/Domain/Model/DailyProcedureEntity.swift (1)
10-15: 적절한 도메인 엔티티 구조입니다.
Hashable준수로 SwiftUI에서 활용하기 좋고, 필요한 필드들을 잘 갖추고 있습니다.
type프로퍼티의 enum 변환 제안은 이전 리뷰에서 이미 언급되었습니다.Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift (1)
21-24: Mock에서 실제 Repository로 전환 완료
MockCalendarRepository에서DefaultCalendarRepository로 올바르게 변경되었습니다.클로저에서
self를 강하게 캡처하는 메모리 누수 문제는 이전 리뷰에서 이미 언급되었습니다.Cherrish-iOS/Cherrish-iOS/Data/Model/Calendar/CalendarDowntimeResponseDTO.swift (1)
10-18: DTO 구조가 적절합니다.
scheduledAt필드가toEntity()매핑에서 사용되지 않는 문제는 이전 리뷰에서 이미 언급되었습니다.Cherrish-iOS/Cherrish-iOS/Data/Model/Calendar/CalendarMonthlyResponseDTO.swift (1)
14-22: 키 변환 로직이 올바르게 구현되었습니다.String 키를 Int로 변환하여
MonthlyEntity를 생성하는 로직이 적절합니다.숫자가 아닌 키 변환 실패 시 로깅 추가 제안은 이전 리뷰에서 이미 언급되었습니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarCellView.swift (1)
26-26:isDDay는let으로 선언하는 것이 적절합니다.이 제안은 이전 리뷰에서 이미 언급되었습니다.
Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchTodayProcedureListUseCase.swift (1)
10-24: 구현이 올바릅니다. 네이밍 일관성 검토를 권장합니다.Use case 구현 로직이 정확하며
CalendarInterface와 올바르게 연동됩니다.다만, 구현체 이름
DefaultFetchTodayProcedureUseCase에서 "List"가 누락되어 프로토콜명FetchTodayProcedureListUseCase와 불일치합니다. 일관성을 위해DefaultFetchTodayProcedureListUseCase로 변경을 고려하세요.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift (2)
123-126: 시간대 고정 후 다른 계산과의 일관성 확인 필요.이전 리뷰에서 지적된 문제입니다. KST로 고정했지만
isSelected,extractDate등 다른 메서드들은Calendar.current를 사용합니다. 디바이스 타임존이 다를 때 월/일 계산이 달라질 수 있습니다.
109-112: 실패 시 상태 불일치 가능—업데이트 순서 조정 필요.이전 리뷰에서 지적된 문제가 아직 수정되지 않았습니다. fetch 실패 시
treatmentDate만 갱신되고procedureList는 이전 값으로 남습니다.🔧 수정 제안
`@MainActor` func fetchTodayProcedureList() async throws { - treatmentDate = selectedDate.toDateString() - procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate) + let dateString = selectedDate.toDateString() + let list = try await fetchTodayProcedureListUseCase.execute(date: dateString) + treatmentDate = dateString + procedureList = list }Cherrish-iOS/Cherrish-iOS/Data/Repository/CalendarRepository.swift (2)
14-19: 주입된 의존성이 무시되고 있습니다.이전 리뷰에서 지적된 문제가 아직 수정되지 않았습니다. 초기화 매개변수를 받지만 항상 기본 구현체로 덮어쓰고 있어 DI가 무력화됩니다.
🔧 수정 제안
init( - networkService: NetworkService, - userDefaultService: UserDefaultService + networkService: NetworkService = DefaultNetworkService(), + userDefaultService: UserDefaultService = DefaultUserDefaultService() ) { - self.networkService = DefaultNetworkService() - self.userDefaultService = DefaultUserDefaultService() + self.networkService = networkService + self.userDefaultService = userDefaultService }
64-90: MockCalendarRepository가 CalendarInterface 프로토콜을 만족하지 않습니다.이전 리뷰에서 지적된 문제가 아직 수정되지 않았습니다.
fetchProcedureCountOfMonth와fetchTodayProcedureList에async throws가 누락되어 프로토콜 요구사항과 맞지 않아 컴파일 오류가 발생합니다.🔧 수정 제안
- func fetchProcedureCountOfMonth(year: Int, month: Int) -> MonthlyEntity { + func fetchProcedureCountOfMonth(year: Int, month: Int) async throws -> MonthlyEntity { return MonthlyEntity.init( dailyProcedureCounts: [ ) } - func fetchTodayProcedureList(date: String) -> [DailyProcedureEntity] { + func fetchTodayProcedureList(date: String) async throws -> [DailyProcedureEntity] {Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift (3)
50-58: 월 변경 시 데이터 갱신이 필요합니다.이전 리뷰에서 지적된 문제가 아직 수정되지 않았습니다.
calendarMode == .none가드로 인해 시술 상세 모드에서 월을 변경해도fetchProcedureCountsOfMonth()가 실행되지 않아 달력 데이터가 갱신되지 않습니다.🔧 수정 제안
.task (id: viewModel.currentMonth){ - if calendarMode == .none { - do { - try await viewModel.fetchProcedureCountsOfMonth() - try await viewModel.fetchTodayProcedureList() - } catch { - CherrishLogger.error(error) - } + do { + try await viewModel.fetchProcedureCountsOfMonth() + try await viewModel.fetchTodayProcedureList() + } catch { + CherrishLogger.error(error) } }
113-113:selectedProcedureID ?? 0사용 시 잠재적 문제.이전 리뷰에서 지적된 문제입니다.
0이 유효한 procedure ID라면 의도치 않은 D-Day 표시가 될 수 있습니다.-1같은 무효한 값을 사용하거나 Optional을 직접 전달하는 것이 안전합니다.
177-189: 연속 탭 시 race condition 가능성.이전 리뷰에서 지적된 문제가 아직 수정되지 않았습니다. 빠르게 연속으로 다른 procedure를 탭하면 이전 요청이 완료되기 전에 새 요청이 시작되어
selectedDowntime이 현재 선택과 맞지 않는 데이터로 업데이트될 수 있습니다.🔧 수정 제안: Task 취소 처리 추가
View에 상태 추가:
`@State` private var currentDowntimeTask: Task<Void, Never>? = nilonTapGesture 수정:
.onTapGesture { calendarMode.toggle() selectedProcedureID = procedure.procedureId + currentDowntimeTask?.cancel() - Task { + currentDowntimeTask = Task { do { try await viewModel.fetchDowntimeByDay(procedureId: procedure.procedureId) } catch { + if Task.isCancelled { return } CherrishLogger.error(error) } } }
| guard let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else { | ||
| return | ||
| } | ||
|
|
||
| guard let treatmentCategoryRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else { | ||
| return | ||
| } |
There was a problem hiding this comment.
사용되지 않는 dependency resolution을 제거하세요.
treatmentCategoryRepository가 Line 28-30에서 resolve되지만 이후 코드에서 전혀 사용되지 않습니다. Line 60에서 동일한 타입(TreatmentInterface)을 treatmentRepository로 다시 resolve하고 있습니다.
불필요한 guard 문을 제거하거나, 하나의 resolution으로 통합하세요.
♻️ 제안된 수정
- guard let treatmentCategoryRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else {
- return
- }
- 📝 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 homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else { | |
| return | |
| } | |
| guard let treatmentCategoryRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else { | |
| return | |
| } | |
| guard let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else { | |
| return | |
| } |
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift` around
lines 24 - 30, Remove the redundant dependency resolution for
TreatmentInterface: delete the unused guard that assigns
treatmentCategoryRepository (the guard using DIContainer.shared.resolve(type:
TreatmentInterface.self) at the top) and rely on the existing resolution that
assigns treatmentRepository later (the resolve for TreatmentInterface in
treatmentRepository). Ensure only one DIContainer.shared.resolve(type:
TreatmentInterface.self) remains and that all uses reference treatmentRepository
so no unused variables remain.
| guard let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else { | ||
| return | ||
| } |
There was a problem hiding this comment.
중복된 homeRepository resolution을 제거하세요.
homeRepository가 Line 24-26에서 이미 resolve되었는데, Line 40-42에서 동일한 타입으로 다시 resolve하고 있습니다. 이는 불필요한 중복이며, 첫 번째 resolution이 실패하면 함수가 이미 반환되므로 두 번째 guard는 도달할 수 없거나 불필요합니다.
♻️ 제안된 수정
DIContainer.shared.register(type: FetchTodayProcedureListUseCase.self) {
return DefaultFetchTodayProcedureUseCase(repository: calendarRepository)
}
- guard let homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else {
- return
- }
-
DIContainer.shared.register(type: FetchProcedureDowntimeUseCase.self) {📝 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 homeRepository = DIContainer.shared.resolve(type: HomeInterface.self) else { | |
| return | |
| } |
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift` around
lines 40 - 42, Remove the duplicate resolution of HomeInterface: delete the
second guard that calls DIContainer.shared.resolve(type: HomeInterface.self) and
instead reuse the earlier homeRepository obtained on lines 24-26 (the originally
resolved variable named homeRepository) in the code paths that follow; ensure
there are no shadowed variables with the same name and that any subsequent
references use the initial homeRepository instance rather than resolving again.
| return topGlobalY < initial - 0.1 | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
들여쓰기 불일치.
닫는 중괄호의 들여쓰기가 2칸으로 되어 있습니다. 프로젝트의 다른 부분과 일관성을 위해 4칸으로 맞추는 것을 권장합니다.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
at line 330, The closing brace in CalendarView.swift for the CalendarView
declaration/closure is indented with 2 spaces instead of the project's 4-space
style; locate the mismatched brace in the CalendarView (e.g., end of
struct/extension or body where the lone "}" appears) and re-indent that closing
brace to 4 spaces so it matches the rest of the file and project coding style.
| func isDDay(for date: Date, selectedProcedureID: Int) -> Bool { | ||
| guard let selectedDowntime, selectedDowntime.procedureId == selectedProcedureID else { return false } | ||
| let formatter = DateFormatter() | ||
| formatter.dateFormat = "yyyy-MM-dd" | ||
| formatter.locale = Locale(identifier: "ko_KR") | ||
| formatter.timeZone = TimeZone(identifier: "Asia/Seoul") | ||
|
|
||
| guard let targetDate = formatter.date(from: selectedDowntime.recoveryTargetDate) else { return false } | ||
| var calendar = Calendar(identifier: .gregorian) | ||
| calendar.timeZone = TimeZone(identifier: "Asia/Seoul")! | ||
|
|
||
| let result = calendar.isDate(date, inSameDayAs: targetDate) | ||
| return result | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
DateFormatter를 매번 생성하는 것은 비효율적입니다.
isDDay가 캘린더 셀마다 호출될 수 있어 DateFormatter를 반복 생성하면 성능에 영향을 줄 수 있습니다. 정적 프로퍼티나 lazy 프로퍼티로 캐싱하는 것을 권장합니다.
🔧 수정 제안
// CalendarViewModel 내부에 추가
private static let kstDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "ko_KR")
formatter.timeZone = TimeZone(identifier: "Asia/Seoul")
return formatter
}()
private static let kstCalendar: Calendar = {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Asia/Seoul") ?? .current
return calendar
}()
func isDDay(for date: Date, selectedProcedureID: Int) -> Bool {
guard let selectedDowntime, selectedDowntime.procedureId == selectedProcedureID else { return false }
guard let targetDate = Self.kstDateFormatter.date(from: selectedDowntime.recoveryTargetDate) else { return false }
return Self.kstCalendar.isDate(date, inSameDayAs: targetDate)
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 78 - 91, isDDay currently creates a DateFormatter and Calendar on
every call which is costly; add cached static properties (e.g., private static
let kstDateFormatter: DateFormatter and private static let kstCalendar:
Calendar) inside CalendarViewModel configured with "yyyy-MM-dd",
Locale(identifier: "ko_KR") and TimeZone(identifier: "Asia/Seoul") and replace
the local formatter/calendar creation in isDDay(for:selectedProcedureID:) to use
Self.kstDateFormatter and Self.kstCalendar (avoid force-unwrapping
timeZone—provide a sensible fallback) while keeping the existing guard checks
for selectedDowntime and parsing the target date.
| var calendar = Calendar(identifier: .gregorian) | ||
| calendar.timeZone = TimeZone(identifier: "Asia/Seoul")! |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
TimeZone 강제 언래핑 개선 권장.
TimeZone(identifier:)가 nil을 반환할 가능성이 있어 강제 언래핑보다 안전한 처리를 권장합니다. "Asia/Seoul"은 유효한 식별자이므로 실제 크래시 가능성은 낮지만, 방어적 코딩 관점에서 개선이 좋습니다.
🔧 수정 제안 (isDDay 및 getCurrentMonth 모두 적용)
- calendar.timeZone = TimeZone(identifier: "Asia/Seoul")!
+ calendar.timeZone = TimeZone(identifier: "Asia/Seoul") ?? .current📝 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.
| var calendar = Calendar(identifier: .gregorian) | |
| calendar.timeZone = TimeZone(identifier: "Asia/Seoul")! | |
| var calendar = Calendar(identifier: .gregorian) | |
| calendar.timeZone = TimeZone(identifier: "Asia/Seoul") ?? .current |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 86 - 87, Replace the forced unwrap of TimeZone(identifier:
"Asia/Seoul") when creating the Calendar with safe handling: obtain the timezone
using guard/if-let (e.g., guard let seoulTZ = TimeZone(identifier: "Asia/Seoul")
else { ... }) or provide a fallback (e.g., TimeZone.current) and use that value
to set calendar.timeZone; apply the same change in places referenced by isDDay
and getCurrentMonth so neither uses a forced unwrap of TimeZone(identifier:).
| func toDateString() -> String { | ||
| return Date.dateFormatter.string(from: self) | ||
| } | ||
| } | ||
|
|
||
| extension Date { | ||
| static let dateFormatter: DateFormatter = { | ||
| let formatter = DateFormatter() | ||
| formatter.dateFormat = "yyyy-MM-dd" | ||
| formatter.locale = Locale(identifier: "ko_KR") | ||
| formatter.timeZone = TimeZone(secondsFromGMT: 0) | ||
|
|
||
| return formatter.string(from: self) | ||
| } | ||
| formatter.timeZone = TimeZone(identifier: "Asia/Seoul") | ||
| return formatter | ||
| }() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the file and verify it exists
find . -name "Date+.swift" -o -name "Date+Extension.swift" | head -20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 139
🏁 Script executed:
# Search for toDateString() call sites across the codebase
rg -n --type=swift "toDateString\s*\(" -B2 -A2 | head -100Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1874
🏁 Script executed:
# Also check imports and usage context
rg -n --type=swift "Date\." | grep -i format | head -20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 400
🏁 Script executed:
# Check where getDowntimeState is called
rg -n --type=swift "getDowntimeState" -B3 -A3Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2067
🏁 Script executed:
# Get more context around line 74 in CalendarViewModel
sed -n '70,80p' "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 515
🏁 Script executed:
# Check the full context of fetchTodayProcedureList and getDowntimeState methods
sed -n '72,115p' "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1837
🏁 Script executed:
# Check CalendarView context around line 112
sed -n '100,120p' "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1013
🏁 Script executed:
# Check if CalendarView or its parent has MainActor annotation
rg -n --type=swift "@MainActor" "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift" | head -5Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 52
🏁 Script executed:
# Check CalendarViewModel for `@MainActor` class-level annotation
sed -n '1,30p' "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1142
getDowntimeState() 메서드에 @MainActor 추가하여 스레드 안전성 명시
DateFormatter는 스레드 안전하지 않으며, 현재 코드는 메인 스레드에서만 호출되고 있습니다. 그러나 getDowntimeState()는 @MainActor 어노테이션이 없어 향후 실수로 백그라운드 스레드에서 호출될 경우 크래시 위험이 있습니다. fetchTodayProcedureList()처럼 명시적으로 @MainActor를 추가하여 컴파일 타임에 스레드 안전성을 보장하세요.
🔧 제안 수정안
- func getDowntimeState(for date: Date) -> DowntimeDayState {
+ `@MainActor`
+ func getDowntimeState(for date: Date) -> DowntimeDayState {
let key = date.toDateString()
return downtimeByDay[key] ?? .none
}🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date`+.swift around
lines 20 - 32, The getDowntimeState() method must be annotated with `@MainActor`
to ensure calls that use the shared Date.dateFormatter run on the main thread;
update the declaration of getDowntimeState() to include `@MainActor` (same pattern
used by fetchTodayProcedureList()) so the compiler enforces main-thread
execution whenever getDowntimeState() accesses Date.dateFormatter or other
UI/main-only resources.
🔗 연결된 이슈
📄 작업 내용