-
Notifications
You must be signed in to change notification settings - Fork 1
Meal과 TimeTable 결과를 save하여 cache-first로 동작하도록 구성 #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🛠️ 이슈와 PR의 Labels 동기화를 스킵했어요. |
✅ PR의 Assign 자동 지정을 성공했어요! |
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough주간 모드가 기본값(.weekly)으로 전환되고 일간 관련 분기 및 레거시 코어( Changes
Sequence Diagram(s)sequenceDiagram
participant UI as MainView / User
participant Core as WeeklyMealCore / WeeklyTimeTableCore
participant Client as MealClient / TimeTableClient
participant DB as LocalDatabaseClient
participant API as NeisAPI
Note over UI,Core: 사용자가 날짜(또는 displayDate) 변경
UI->>Core: refreshData 액션
Core->>Client: fetch(date)
Client->>DB: readRecordByColumn(record:, column:"date", value: key)
alt 캐시 히트
DB-->>Client: cachedRecord
Client-->>Core: cached Domain Model
Client->>API: Task (background) syncFromServer(date)
API-->>DB: upsert 최신 데이터
else 캐시 미스
Client->>API: fetchFromServer(date)
API-->>Client: serverData
Client->>DB: upsert per-date entity
DB-->>Client: 저장 완료
Client-->>Core: server Domain Model
end
Core-->>UI: 화면 갱신
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 주의 집중 영역:
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
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 |
Summary of ChangesHello @baekteun, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 Meal 및 TimeTable 데이터 로딩 방식을 캐시 우선(cache-first) 전략으로 전환하여 사용자 경험을 개선하고 오프라인 접근성을 높이는 것을 목표로 합니다. 이를 위해 로컬 데이터베이스에 식단 및 시간표 데이터를 저장하고 관리하는 새로운 엔티티와 로직이 도입되었습니다. 또한, 기존의 일별/주별 뷰 분리 로직을 주별 뷰로 통합하여 코드 복잡성을 줄이고 일관된 사용자 인터페이스를 제공합니다. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
…nto feature/cache-first-meal-timetable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
이 PR은 급식 및 시간표 데이터를 로컬 데이터베이스에 캐시하여 'cache-first' 전략을 구현하는 변경 사항을 담고 있습니다. 이를 통해 앱 성능과 사용자 경험이 개선될 것으로 기대됩니다. 전반적인 구현 방향은 훌륭하지만, 몇 가지 개선할 점이 보입니다. 특히 데이터베이스 상호작용의 효율성, 캐시 로직의 정확성, 그리고 코드 일관성 측면에서 몇 가지 피드백을 드립니다. 자세한 내용은 각 파일의 주석을 참고해주세요.
Projects/Shared/Entity/Sources/Local/TimeTableLocalEntity.swift
Outdated
Show resolved
Hide resolved
| try? localDatabaseClient.delete(record: TimeTableLocalEntity.self, key: entity.id) | ||
| try? localDatabaseClient.save(record: entity) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealCore.swift (1)
71-74: 빈 catch 블록은 에러를 숨깁니다.알레르기 정보 로딩 실패 시 에러가 무시되고 있습니다. 최소한 로깅을 추가하거나, 사용자에게 기본 동작을 명확히 하는 것이 좋습니다.
do { state.allergyList = try localDatabaseClient.readRecords(as: AllergyLocalEntity.self) .compactMap { AllergyType(rawValue: $0.allergy) ?? nil } -} catch {} +} catch { + // 알레르기 정보 로딩 실패 시 빈 목록으로 진행 + state.allergyList = [] +}
🧹 Nitpick comments (8)
Projects/Shared/Entity/Sources/Meal.swift (1)
38-40: LGTM! 다만 중복 코드 제거를 권장합니다.새로운
isEmpty속성이 올바르게 구현되었습니다.WeeklyMealCore.State.DayMeal.isEmpty(lines 27-31)에서 동일한 로직이 중복되어 있으니, 해당 코드를 이 속성을 활용하도록 리팩토링하면 좋겠습니다.
WeeklyMealCore.swift의DayMeal.isEmpty를 다음과 같이 단순화할 수 있습니다:public var isEmpty: Bool { meal.isEmpty }Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealCore.swift (1)
64-68: 구독이 중복 등록될 수 있습니다.
onLoad가 여러 번 호출되면$displayDate.publisher구독이 중복으로 등록될 수 있습니다. 취소 ID를 추가하여 기존 구독을 취소하거나, 한 번만 호출되도록 보장하는 것이 좋습니다.+ private enum CancellableID: Hashable { + case fetch + case displayDateSubscription + }case .onLoad: - return .publisher { + return .cancel(id: CancellableID.displayDateSubscription) + .merge(with: .publisher { state.$displayDate.publisher .map { _ in Action.refreshData } - } + }.cancellable(id: CancellableID.displayDateSubscription))Projects/Feature/MainFeature/Sources/MainView.swift (1)
113-118:normalizedWeekStart계산이 중복됩니다.
previousWeek,currentWeekStart,nextWeek는 이미startOfWeek또는previousWeekStart/nextWeekStart를 통해 계산되었으므로, Line 117에서 다시startOfWeek(for: weekStart)를 호출하는 것은 불필요합니다.- ForEach([previousWeek, currentWeekStart, nextWeek], id: \.timeIntervalSince1970) { weekStart in - let normalizedWeekStart = datePolicy.startOfWeek(for: weekStart) + ForEach([previousWeek, currentWeekStart, nextWeek], id: \.timeIntervalSince1970) { normalizedWeekStart inProjects/Shared/Entity/Sources/Local/TimeTableLocalEntity.swift (2)
22-24:persistenceKey메서드가 사용되지 않음
persistenceKey(for:)메서드가 정의되어 있지만, 클라이언트 코드에서는date컬럼을 직접 사용하여 조회하고 있습니다. 관련 코드 스니펫에서MealLocalEntity.swift도 동일한 패턴을 가지고 있어 일관성은 있으나, 사용되지 않는 코드로 보입니다. 향후 사용 계획이 있다면 유지하고, 그렇지 않다면 제거를 고려해 주세요.
49-68: DateFormatter 인스턴스 재사용 권장
toTimeTables()메서드가 호출될 때마다 새로운DateFormatter인스턴스를 생성합니다.DateFormatter는 생성 비용이 높으므로, 정적 프로퍼티로 캐싱하는 것을 권장합니다.+private extension TimeTableLocalEntity { + static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd" + formatter.timeZone = .autoupdatingCurrent + formatter.locale = .autoupdatingCurrent + return formatter + }() +} + public extension TimeTableLocalEntity { // ... func toTimeTables() -> [TimeTable] { let decoder = JSONDecoder() guard let items = try? decoder.decode([TimeTableItem].self, from: Data(timeTableData.utf8)) else { return [] } - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyyMMdd" - dateFormatter.timeZone = .autoupdatingCurrent - dateFormatter.locale = .autoupdatingCurrent - let dateObject = dateFormatter.date(from: date) + let dateObject = Self.dateFormatter.date(from: date) return items.map { item in // ... } } }Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (1)
16-20:formatDate구현이 MealClient와 다릅니다
TimeTableClient에서는 수동 문자열 조합을,MealClient에서는DateFormatter를 사용합니다. 일관성과 유지보수를 위해 공통 유틸리티로 통합하거나 동일한 방식을 사용하는 것을 권장합니다. 관련 코드 스니펫(Projects/Shared/MealClient/Sources/MealClient.swift, Lines 14-19)을 참고하세요.Projects/Feature/MainFeature/Sources/MainCore.swift (2)
38-69: 주간 모드 기본값으로 바뀌면서dateSelectionMode가 사실상 상수가 되었습니다리듀서 내에서 더 이상
dateSelectionMode를 바꾸는 액션이 없어 항상.weekly로만 동작합니다. 일별 모드를 완전히 제거한 것이라면DateSelectionMode.daily와.daily분기, 관련 로직들을 정리해 두면 상태 정의가 더 단순해질 것 같습니다.
161-168:.refresh분기에서DatePolicy를 생성만 하고 사용하지 않아 사실상 no-op입니다
weeklyMealCore(.refresh)/weeklyTimeTableCore(.refresh)케이스에서isSkipWeekend,isSkipAfterDinner,datePolicy를 계산하지만 상태 변경이나 이펙트가 없어 동작에 영향을 주지 않습니다.
refresh 시점에 더 이상 날짜 보정이 필요 없다면 이 케이스를 통째로 제거하고, 여전히DatePolicy기반 보정이 필요하다면displayDate갱신 등 실제 동작으로 연결해 두는 편이 명확할 것 같습니다. 기존 요구사항과 비교해 한 번만 확인 부탁드립니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
Projects/Feature/MainFeature/Sources/MainCore.swift(4 hunks)Projects/Feature/MainFeature/Sources/MainView.swift(2 hunks)Projects/Feature/MealFeature/Sources/Weekly/WeeklyMealCore.swift(1 hunks)Projects/Shared/Entity/Sources/Local/MealLocalEntity.swift(1 hunks)Projects/Shared/Entity/Sources/Local/TimeTableLocalEntity.swift(1 hunks)Projects/Shared/Entity/Sources/Meal.swift(1 hunks)Projects/Shared/LocalDatabaseClient/Sources/LocalDatabaseClient.swift(2 hunks)Projects/Shared/MealClient/Project.swift(1 hunks)Projects/Shared/MealClient/Sources/MealClient.swift(4 hunks)Projects/Shared/TimeTableClient/Project.swift(1 hunks)Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
Projects/Shared/Entity/Sources/Meal.swift (1)
Projects/App/macOS-Widget/Sources/MealWidgetEntryView.swift (1)
meals(48-59)
Projects/Feature/MainFeature/Sources/MainView.swift (3)
Projects/Feature/MainFeature/Sources/DatePolicy.swift (4)
startOfWeek(106-112)previousWeekStart(119-122)nextWeekStart(124-127)weekDisplayText(35-56)Projects/Shared/TWLog/Sources/TWLog.swift (1)
event(63-68)Projects/UserInterface/DesignSystem/Sources/TWFont/Font+tw.swift (2)
twFont(4-11)twFont(13-18)
Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (7)
Projects/Shared/MealClient/Sources/MealClient.swift (1)
formatDate(15-20)Projects/Shared/LocalDatabaseClient/Sources/LocalDatabaseClient.swift (6)
readRecordByColumn(43-51)save(11-15)save(17-23)readRecordsByColumn(53-61)delete(77-83)delete(85-92)Projects/Shared/Entity/Sources/Local/TimeTableLocalEntity.swift (1)
toTimeTables(49-68)Projects/Shared/NeisClient/Sources/NeisClient.swift (1)
fetchDataOnNeis(8-32)Projects/Shared/EnumUtil/Sources/SchoolType.swift (1)
toSubURL(9-20)Projects/Shared/TimeTableClient/Sources/SingleTimeTableResponseDTO.swift (1)
toDomain(25-33)Projects/Shared/SchoolClient/Sources/SingleSchoolResponseDTO.swift (1)
toDomain(36-44)
Projects/Shared/MealClient/Sources/MealClient.swift (4)
Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (1)
formatDate(16-20)Projects/Shared/LocalDatabaseClient/Sources/LocalDatabaseClient.swift (4)
readRecordByColumn(43-51)save(11-15)save(17-23)readRecordsByColumn(53-61)Projects/Shared/Entity/Sources/Local/MealLocalEntity.swift (1)
toMeal(60-71)Projects/Shared/NeisClient/Sources/NeisClient.swift (1)
fetchDataOnNeis(8-32)
Projects/Shared/Entity/Sources/Local/TimeTableLocalEntity.swift (1)
Projects/Shared/Entity/Sources/Local/MealLocalEntity.swift (1)
persistenceKey(37-39)
Projects/Shared/Entity/Sources/Local/MealLocalEntity.swift (1)
Projects/Shared/Entity/Sources/Local/TimeTableLocalEntity.swift (1)
persistenceKey(22-24)
⏰ 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: 🧪 Test
🔇 Additional comments (13)
Projects/Shared/Entity/Sources/Local/MealLocalEntity.swift (2)
46-58: 인코딩 실패 시 조용히 빈 배열로 대체됩니다.
try?로 인해 JSON 인코딩 실패가 무시되고"[]"로 저장됩니다. 이는 의도된 동작일 수 있지만, 디버깅 시 데이터 손실을 알아채기 어려울 수 있습니다.
5-6: Schema already prevents duplicate records for same date via unique constraint with automatic conflict resolution.The review comment's concerns about duplicate records are unfounded. The database schema in
LocalDatabaseClient.swift(line 243) defines thedatecolumn as.unique(onConflict: .replace), which automatically replaces any existing record with the same date when inserting. GRDB'sinsert()method respects this constraint, making explicit upsert logic unnecessary.The implementation correctly handles the cache-first pattern through schema-level enforcement rather than application logic.
Projects/Shared/MealClient/Project.swift (1)
12-12: LGTM!캐시 기능 구현을 위해
LocalDatabaseClient의존성이 올바르게 추가되었습니다.Projects/Shared/TimeTableClient/Project.swift (1)
15-16: LGTM!캐시 기능 구현을 위해
UserDefaultsClient와LocalDatabaseClient의존성이 올바르게 추가되었습니다. MealClient와 일관된 패턴입니다.Projects/Feature/MainFeature/Sources/MainView.swift (1)
53-72: LGTM!
IfLetStore를 사용한 optional state 스코핑이 적절하게 구현되어 있습니다. Weekly view로의 통합이 깔끔하게 이루어졌습니다.Projects/Shared/LocalDatabaseClient/Sources/LocalDatabaseClient.swift (2)
43-61: LGTM!GRDB의
Column필터링을 활용한 새로운 쿼리 헬퍼 메서드들이 잘 구현되어 있습니다. 단일 값 조회와 다중 값 조회 모두 적절한 패턴을 사용하고 있습니다.
240-259: Primary key와 unique 제약조건 충돌 가능성 검토 필요
id가 primary key이고date가 unique 제약조건을 가지고 있는데, 둘 다onConflict: .replace를 사용합니다. 동일한date로 다른id를 가진 레코드를 삽입하면, unique 제약조건의 replace가 적용되지만 기존 레코드가 다른 primary key를 가지고 있어 예상치 못한 동작이 발생할 수 있습니다.클라이언트 코드(TimeTableClient, MealClient)에서 저장 전 기존 레코드를 삭제하는 방식으로 이를 처리하고 있으나,
insert대신 GRDB의save()(insert or update)를 사용하거나,date를 primary key로 사용하는 것이 더 안전할 수 있습니다.Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (1)
149-190: 중복 API 호출 패턴에 대한 확인전공(major) 파라미터를 포함한 첫 번째 API 호출이 실패하면 전공 없이 재시도하는 fallback 패턴으로 보입니다. 의도된 동작이라면 괜찮으나, 향후 유지보수를 위해 이 로직을 별도 헬퍼 함수로 추출하거나 주석을 추가하는 것을 고려해 주세요.
Projects/Shared/MealClient/Sources/MealClient.swift (2)
98-115: 캐시 유효성 검사 로직 검토 필요
hasNonEmptyCache는 모든 캐시된 식단이 비어있지 않아야true가 됩니다. 특정 날짜에 식단이 제공되지 않는 경우(예: 공휴일, 방학)에도 빈 값으로 캐시될 수 있는데, 이 경우 불필요하게 서버 요청이 발생할 수 있습니다.의도된 동작인지 확인이 필요합니다. 부분 캐시도 유효하다면
!result.isEmpty만으로 충분할 수 있습니다.
100-124: LGTM!
TimeTableClient와 달리hasNonEmptyCache조건으로 백그라운드 동기화를 감싸고 있어, 캐시가 비어있을 때 중복 네트워크 요청이 발생하지 않습니다.Projects/Feature/MainFeature/Sources/MainCore.swift (3)
153-158:weeklyMealCore/weeklyTimeTableCore를onAppear에서 지연 초기화하는 구조가 적절합니다
nil일 때만 초기화해서 기존 서브 상태(및 캐시)를 재사용하도록 한 덕분에, 의도하신 cache-first 전략과 잘 맞는 구현으로 보입니다.
178-182: 설정 버튼 액션을 하나의 케이스로 합친 부분이 깔끔합니다상단 탭 액션(
.settingButtonDidTap)과WeeklyMealCore내부 액션 모두 동일하게 설정 화면을 띄우고 로그를 남기므로, 하나의 분기로 합쳐 처리한 현재 구현은 중복을 줄이면서 의도도 명확하게 드러나 좋습니다.
253-258: 주간 코어만subFeatures()에 연결한 구조가 의도와 잘 맞습니다
weeklyMealCore/weeklyTimeTableCore만ifLet으로 연결해 MainCore를 주간 전용으로 정리한 점이 일관적입니다. 사용하는 TCA 버전에서action: \.weeklyMealCore/\.weeklyTimeTableCore형태의 키패스 매핑이 지원되는지만 한 번 확인해 주시면 좋겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (3)
16-20:DateFormatter사용으로 일관성 유지 권장
MealClient에서는DateFormatter를 사용하는 반면, 여기서는 문자열 조합 방식을 사용하고 있습니다.Locale처리와 코드베이스 일관성을 위해DateFormatter사용을 권장합니다.private func formatDate(_ date: Date) -> String { - let month = date.month < 10 ? "0\(date.month)" : "\(date.month)" - let day = date.day < 10 ? "0\(date.day)" : "\(date.day)" - return "\(date.year)\(month)\(day)" + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd" + formatter.locale = Locale(identifier: "ko_kr") + return formatter.string(from: date) }
149-190: 중첩 do-catch 블록 간소화 가능첫 번째 요청 실패 시
try?와 nil-coalescing을 사용하면 더 간결하게 표현할 수 있습니다.do { response = try await neisClient.fetchDataOnNeis( type.toSubURL(), queryItem: [ .init(name: "KEY", value: key), .init(name: "Type", value: "json"), .init(name: "pIndex", value: "1"), .init(name: "pSize", value: "100"), .init(name: "ATPT_OFCDC_SC_CODE", value: orgCode), .init(name: "SD_SCHUL_CODE", value: code), .init(name: "DDDEP_NM", value: major), .init(name: "GRADE", value: "\(grade)"), .init(name: "CLASS_NM", value: "\(`class`)"), .init(name: "TI_FROM_YMD", value: reqDate), .init(name: "TI_TO_YMD", value: reqDate) ], key: type.toSubURL(), type: [SingleTimeTableResponseDTO].self ) } catch { - do { - response = try await neisClient.fetchDataOnNeis( + response = (try? await neisClient.fetchDataOnNeis( type.toSubURL(), queryItem: [ .init(name: "KEY", value: key), ... ], key: type.toSubURL(), type: [SingleTimeTableResponseDTO].self - ) - } catch { - response = [] - } + )) ?? [] }
100-126: 캐시가 비어있을 때 중복 네트워크 요청 발생
Task.detached가 항상 시작되고, 캐시가 비어있으면fetchTimeTableRangeFromServer도 호출됩니다. 캐시가 없는 경우 동일한 서버 요청이 두 번 발생합니다.
fetchTimeTable과 동일하게 백그라운드 동기화는 캐시된 데이터를 반환하는 경우에만 실행되도록 수정하세요.+ if !cachedTimeTables.isEmpty { Task.detached { await syncTimeTableRangeFromServer( startAt: startAt, endAt: endAt, type: type, orgCode: orgCode, code: code, grade: grade, classNum: `class`, major: major ) } + return cachedTimeTables + } if cachedTimeTables.isEmpty { return await fetchTimeTableRangeFromServer( startAt: startAt, endAt: endAt, type: type, orgCode: orgCode, code: code, grade: grade, classNum: `class`, major: major ) } - - return cachedTimeTablesProjects/Shared/MealClient/Sources/MealClient.swift (1)
85-115:hasNonEmptyCache조건이 지나치게 느슨해 부분 캐시에서도 서버 응답을 즉시 사용하지 않음현재는
let hasNonEmptyCache = !result.isEmpty로, 요청한 기간 중 하루라도 캐시가 있으면 나머지 날짜는 모두Meal.empty로 채우고 네트워크 응답은 백그라운드 동기화에만 사용합니다. 이렇게 되면 한 번도 조회한 적 없는 날짜들도 처음에는 빈 값으로 보였다가, 나중에야 실제 급식이 채워지는 패턴이 되어 UX가 다소 예측하기 어려울 수 있습니다.이전 리뷰 코멘트에서 제안되었던 것처럼, 최소한
result.count == dateStrings.count일 때만 “요청한 모든 날짜가 캐시됨”으로 보고 cache‑first로 동작시키고, 그렇지 않으면 바로fetchMealsFromServer를 호출해 첫 응답부터 서버 데이터를 반영하는 쪽이 더 직관적으로 보입니다.
물론 현재 구현이 “부분 캐시라도 우선 보여주고, 백그라운드에서 채운다”는 명시적인 의도라면 그대로 유지해도 되므로, 의도와 맞는지 한 번 더 확인해 보시면 좋겠습니다.
🧹 Nitpick comments (3)
Projects/Shared/MealClient/Sources/MealClient.swift (3)
15-20: DateFormatter 재사용 및 날짜 포맷 로직 통합 제안
formatDate(_:)와fetchMealsFromServer내부에서 동일한 포맷(yyyyMMdd,ko_kr)의DateFormatter를 각각 새로 생성하고 있습니다. 호출 빈도가 높을 수 있으니 정적 프로퍼티나 캐시된 인스턴스로 재사용하고,fetchMealsFromServer에서formatter.string(from:)대신formatDate(_:)를 재사용하면 날짜 포맷 로직이 한 곳에 모여 유지보수성이 좋아집니다.Also applies to: 187-190
39-51: 단일 날짜 캐시에서Meal.empty처리 전략 재점검 권장단일 조회 경로에서 캐시를 읽은 뒤
!cachedMeal.isEmpty인 경우에만 캐시를 신뢰하고, 그렇지 않으면 매번fetchMealFromServer를 호출합니다. 현재fetchMealFromServer는 네트워크 오류나 실제 급식 미제공(공휴일 등) 모두에 대해Meal.empty를 반환하므로, 공휴일처럼 진짜로 급식이 없는 날에도 매 호출마다 서버를 두드릴 수 있습니다.
Meal.empty가 “정상적으로 급식이 없는 날”을 표현하는 용도로도 쓰인다면, 이 부분을 한 번 더 점검해 보시고 필요하다면 에러/정상 상태를 구분할 수 있는 메타데이터나 플래그를 도입하는 것도 고려해 볼 만합니다.Also applies to: 129-160
47-49:Task.detached사용 시 Dependencies 환경 전파 여부 확인 필요
fetchMeal/fetchMeals에서Task.detached로syncMealFromServer와syncMealsFromServer를 호출하고, 이들 함수 내부에서@Dependency를 사용하고 있습니다. Point‑FreeDependencies기준으로는Task.detached가 의존성 환경을 승계하지 않아, 테스트에서 override한neisClient나localDatabaseClient대신 라이브 의존성이 호출될 수 있는 여지가 있습니다.가능하다면
Task { ... }(structured task)로 변경하거나,Task.detached안에서 필요한 의존성을 캡처해 파라미터로 넘기는 식으로백그라운드 작업에서도 동일한 의존성 환경이 보장되도록 조정하는 것을 권장드립니다.
Also applies to: 101-108, 253-264
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
Projects/App/iOS/Support/Info.plist(2 hunks)Projects/Shared/MealClient/Sources/MealClient.swift(4 hunks)Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Projects/Shared/MealClient/Sources/MealClient.swift (4)
Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (1)
formatDate(16-20)Projects/Shared/LocalDatabaseClient/Sources/LocalDatabaseClient.swift (4)
readRecordByColumn(43-51)save(11-15)save(17-23)readRecordsByColumn(53-61)Projects/Shared/Entity/Sources/Local/MealLocalEntity.swift (1)
toMeal(60-71)Projects/Shared/NeisClient/Sources/NeisClient.swift (1)
fetchDataOnNeis(8-32)
⏰ 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: ⚙️ Prepare for CI
🔇 Additional comments (5)
Projects/App/iOS/Support/Info.plist (1)
21-22: I'll help you verify the review comment about the Info.plist changes. Let me start by examining the repository structure to understand the multi-target setup and version consistency.
<function_calls>
#!/bin/bashFind all Info.plist files in the repository
fd 'Info.plist' -t f | sort
</function_calls>Please wait while I scan for Info.plist files and check version/build consistency across all targets.
Projects/Shared/TimeTableClient/Sources/TimeTableClient.swift (4)
39-59: 캐시 우선 로직이 올바르게 구현됨캐시된 데이터가 있을 때만 백그라운드 동기화를 트리거하고, 캐시가 비어있으면 서버에서 직접 가져오는 방식이 적절합니다.
197-204: 이전 리뷰 피드백이 반영됨불필요한
delete호출이 제거되고save만 사용하도록 수정되었습니다.date컬럼의 unique 제약 조건이 중복 데이터를 자동으로 대체합니다.
269-276: 날짜별 그룹화 및 저장 로직 적절함
TimeTable을 날짜별로 그룹화하여 각각 저장하는 방식이 캐시 일관성 유지에 적합합니다.date가 nil인 경우도 적절히 처리됩니다.
281-301: 백그라운드 동기화 래퍼 함수서버에서 데이터를 가져와 캐시를 업데이트하는 사이드 이펙트만 필요하므로 반환값을 무시하는 것이 적절합니다.
💡 개요
Meal과 TimeTable 결과를 save하여 cache-first로 동작하도록 구성
Summary by CodeRabbit
릴리스 노트
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.