[Feat] 활동 기록 API 서버 연동 - #93
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Walkthrough활동 기록 화면이 mock 데이터 대신 서버의 월간 배지와 감정 기록을 사용하도록 변경되었습니다. 도메인 모델, API 저장소, DI 주입, 월별 캐시, URL 이미지 로딩 및 달력·상세 화면 표시 흐름이 추가되었습니다. Changes활동 기록 데이터 흐름
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ActivityHistoryViewController
participant ActivityHistoryViewModel
participant ActivityHistoryRepository
participant NetworkService
ActivityHistoryViewController->>ActivityHistoryViewModel: 활동 기록 입력 전달
ActivityHistoryViewModel->>ActivityHistoryRepository: 월간 배지 또는 감정 기록 조회
ActivityHistoryRepository->>NetworkService: ActivityHistoryEndpoint 요청
NetworkService-->>ActivityHistoryRepository: 디코딩된 DTO 반환
ActivityHistoryRepository-->>ActivityHistoryViewModel: 도메인 엔터티 반환
ActivityHistoryViewModel-->>ActivityHistoryViewController: 화면 데이터 발행
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`:
- Line 33: ActivityHistoryViewModel의 networkRetryHandler가 생성만 되고 fetch 실패 처리에
연결되지 않았습니다. fetchMonthlyBadges와 fetchEmotionMarbles의 각 catch 블록에서
DomainError.requireRetry를 구분해 networkRetryHandler를 통해 재시도하고, 재시도 불가능한 오류와 최종 실패는
기존 로깅 및 상태 처리로 유지하세요.
- Line 27: Update the unstructured Task blocks in fetchMonthlyBadge and
fetchEmotionHistory to run with MainActor isolation, including all
emotionHistoryCache access and CurrentValueSubject.send() calls consumed by the
UI. Keep the existing Task-based flow and behavior unchanged while ensuring both
methods’ asynchronous UI updates and dictionary reads/writes execute on the main
actor.
- Around line 93-100: Update the loop building emotionHistoryRecords so an
unrecognized emotionMarbleType skips only that individual emotionHistory record,
rather than returning from the surrounding Task closure. Preserve processing of
all valid records and allow the subsequent emotionHistoryCache update and
monthlyEmotionHistorySubject.send call to execute.
- Around line 44-49: Update action(input:) for .invalidateCacheAndRefetch so
EmotionCalendarView.currentPage is synchronized with the refresh: reset the
calendar page to .now before fetching, or use the current page as the date for
fetchMonthlyBadge and fetchEmotionHistory. Ensure the displayed month and
refreshed data always match when the reused view reappears.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3ab98bef-5e62-441a-a8c5-fae258edfba8
⛔ Files ignored due to path filters (39)
Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/emotion_multiple_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/emotion_single_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/none_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/report_multiple_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/report_single_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/routine_multiple_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/routine_single_badge@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/anxiety_emotion_graphic.imageset/anxiety_emotion_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/anxiety_emotion_graphic.imageset/anxiety_emotion_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/anxiety_emotion_graphic.imageset/anxiety_emotion_graphic@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/calm_emotion_graphic.imageset/calm_emotion_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/calm_emotion_graphic.imageset/calm_emotion_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/calm_emotion_graphic.imageset/calm_emotion_graphic@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/fatigue_emotion_graphic.imageset/fatigue_emotion_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/fatigue_emotion_graphic.imageset/fatigue_emotion_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/fatigue_emotion_graphic.imageset/fatigue_emotion_graphic@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/lethargy_emotion_graphic.imageset/lethargy_emotion_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/lethargy_emotion_graphic.imageset/lethargy_emotion_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/lethargy_emotion_graphic.imageset/lethargy_emotion_graphic@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/satisfaction_emotion_graphic.imageset/satisfaction_emotion_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/satisfaction_emotion_graphic.imageset/satisfaction_emotion_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/satisfaction_emotion_graphic.imageset/satisfaction_emotion_graphic@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/vitality_emotion_graphic.imageset/vitality_emotion_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/vitality_emotion_graphic.imageset/vitality_emotion_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionActivity/vitality_emotion_graphic.imageset/vitality_emotion_graphic@3x.pngis excluded by!**/*.png
📒 Files selected for processing (32)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swiftProjects/DataSource/Sources/DTO/EmotionMarbleHistoryDTO.swiftProjects/DataSource/Sources/DTO/MonthlyBadgesDTO.swiftProjects/DataSource/Sources/Endpoint/ActivityHistoryEndpoint.swiftProjects/DataSource/Sources/Repository/ActivityHistoryRepository.swiftProjects/Domain/Sources/Entity/BadgeEntity.swiftProjects/Domain/Sources/Entity/EmotionHistoryEntity.swiftProjects/Domain/Sources/Protocol/Repository/ActivityHistoryRepositoryProtocol.swiftProjects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/anxiety_emotion_graphic.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/calm_emotion_graphic.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/fatigue_emotion_graphic.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/lethargy_emotion_graphic.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/satisfaction_emotion_graphic.imageset/Contents.jsonProjects/Presentation/Resources/Images.xcassets/EmotionActivity/vitality_emotion_graphic.imageset/Contents.jsonProjects/Presentation/Sources/ActivityHistory/Model/ActivityBadge.swiftProjects/Presentation/Sources/ActivityHistory/Model/EmotionMarble.swiftProjects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swiftProjects/Presentation/Sources/ActivityHistory/View/Component/ActivityBadgeSectionView.swiftProjects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swiftProjects/Presentation/Sources/ActivityHistory/View/EmotionDetailViewController.swiftProjects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swiftProjects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swiftProjects/Presentation/Sources/Common/PresentationDependencyAssembler.swiftProjects/Presentation/Sources/EmotionRegister/Model/Emotion.swift
💤 Files with no reviewable changes (16)
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/calm_emotion_graphic.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Badge/emotion_single_badge.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Badge/routine_multiple_badge.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/lethargy_emotion_graphic.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/vitality_emotion_graphic.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Badge/none_badge.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/anxiety_emotion_graphic.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Badge/routine_single_badge.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/fatigue_emotion_graphic.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Badge/report_multiple_badge.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/Badge/emotion_multiple_badge.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionActivity/satisfaction_emotion_graphic.imageset/Contents.json
- Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift
- Projects/Presentation/Resources/Images.xcassets/Badge/report_single_badge.imageset/Contents.json
- Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift
|
|
||
| init() { | ||
| private let calendar = Calendar.current | ||
| private var emotionHistoryCache: [String: [String: EmotionMarble]] = [:] |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
emotionHistoryCache에 대한 동시성 레이스 컨디션 위험.
fetchMonthlyBadge와 fetchEmotionHistory가 각각 별도의 비구조적 Task {}로 실행되고, fetchEmotionHistory는 그 안에서 emotionHistoryCache(일반 Dictionary, 스레드 안전하지 않음)를 읽고 씁니다. 달력을 빠르게 넘기면 여러 Task가 동시에 실행되어 같은 딕셔너리에 동시 접근할 수 있어 실제 데이터 레이스가 발생할 수 있습니다. 또한 응답이 요청 순서와 다르게 도착하면(느린 이전 달 응답이 나중에 도착) 최신 페이지의 배지/감정 데이터가 과거 응답으로 덮어써질 수 있습니다.
동일 파일의 이전 PR에서 subject를 send()하는 Task {}는 Task { @mainactor in ... }로 감싸라는 학습 내용이 있었습니다. 이 패턴을 적용하면 emotionHistoryCache 접근이 메인 액터로 격리되어 실제 데이터 레이스는 방지되며(순서 역전으로 인한 논리적 stale 표시는 별도 이슈로 남습니다).
🔒 제안: MainActor로 격리
private func fetchEmotionHistory(date: Date) {
let key = monthKey(for: date)
if let cachedEmotionHistory = emotionHistoryCache[key] {
monthlyEmotionHistorySubject.send(cachedEmotionHistory)
return
}
- Task {
+ Task { `@MainActor` in
do {fetchMonthlyBadge에도 동일하게 적용해 주세요.
Based on learnings, in this file's Task{}/CurrentValueSubject.send() pattern consumed by UI sinks, "prefer keeping the Task {} wrapper but change it to Task { MainActor in ... }".
Also applies to: 59-108
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`
at line 27, Update the unstructured Task blocks in fetchMonthlyBadge and
fetchEmotionHistory to run with MainActor isolation, including all
emotionHistoryCache access and CurrentValueSubject.send() calls consumed by the
UI. Keep the existing Task-based flow and behavior unchanged while ensuring both
methods’ asynchronous UI updates and dictionary reads/writes execute on the main
actor.
Source: Learnings
| private let monthlyEmotionHistorySubject = CurrentValueSubject<[String: EmotionMarble], Never>([:]) | ||
|
|
||
| private let activityHistoryRepository: ActivityHistoryRepositoryProtocol | ||
| private let networkRetryHandler: NetworkRetryHandler |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
networkRetryHandler가 생성만 되고 재시도 로직에 전혀 사용되지 않습니다.
activityHistoryRepository는 재시도 가능한 오류를 DomainError.requireRetry로 던지는데(ActivityHistoryRepository.fetchMonthlyBadges/fetchEmotionMarbles 참고), 두 catch 블록 모두 에러 종류를 구분하지 않고 로깅만 하고 끝냅니다. networkRetryHandler가 인스턴스화된 걸 보면 재시도 처리가 의도되었던 것으로 보이나 실제로는 연결되어 있지 않아, 일시적 네트워크 오류 시 사용자가 재시도 없이 빈 화면을 보게 됩니다.
Also applies to: 37-37, 66-68, 104-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`
at line 33, ActivityHistoryViewModel의 networkRetryHandler가 생성만 되고 fetch 실패 처리에
연결되지 않았습니다. fetchMonthlyBadges와 fetchEmotionMarbles의 각 catch 블록에서
DomainError.requireRetry를 구분해 networkRetryHandler를 통해 재시도하고, 재시도 불가능한 오류와 최종 실패는
기존 로깅 및 상태 처리로 유지하세요.
| func action(input: Input) { | ||
| switch input { | ||
| case .invalidateCacheAndRefetch: | ||
| emotionHistoryCache.removeAll() | ||
| fetchMonthlyBadge(date: .now) | ||
| fetchEmotionHistory(date: .now) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant files
fd -a 'ActivityHistoryViewModel.swift|ActivityHistoryViewController.swift' Projects || true
# Show outlines first
ast-grep outline Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift --view expanded || true
ast-grep outline Projects/Presentation/Sources/ActivityHistory/ActivityHistoryViewController.swift --view expanded || true
# Read the relevant ranges with line numbers
sed -n '1,220p' Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift | cat -n
printf '\n==== CONTROLLER ====\n'
sed -n '1,320p' Projects/Presentation/Sources/ActivityHistory/ActivityHistoryViewController.swift | cat -nRepository: YAPP-Github/Bitnagil-iOS
Length of output: 6340
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the correct controller file and any lifecycle/calendar logic
ast-grep outline Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift --view expanded || true
sed -n '1,360p' Projects/Presentation/Sources/ActivityHistory/View/ActivityHistoryViewController.swift | cat -n
printf '\n==== SEARCH FOR RELEVANT CALLS ====\n'
rg -n "invalidateCacheAndRefetch|viewWillAppear|FSCalendar|setCurrentPage|currentPage|selectedDate|tabBar" Projects/Presentation/Sources/ActivityHistory Projects/Presentation/Sources -g '!**/Generated/**' || trueRepository: YAPP-Github/Bitnagil-iOS
Length of output: 33798
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift --view expanded || true
sed -n '1,280p' Projects/Presentation/Sources/ActivityHistory/View/Component/EmotionCalendarView.swift | cat -n
printf '\n==== TAB BAR / PERSISTENCE CONTEXT ====\n'
sed -n '1,140p' Projects/Presentation/Sources/Common/View/TabBarView.swift | cat -nRepository: YAPP-Github/Bitnagil-iOS
Length of output: 14516
캘린더 페이지를 현재 월과 동기화해야 합니다. TabBarView에서 ActivityHistoryViewController가 재사용되는데, viewWillAppear마다 캐시를 지우고 .now 기준으로만 다시 불러오면서 EmotionCalendarView의 calendar.currentPage는 유지됩니다. 다른 달을 보고 나갔다가 돌아오면 화면의 월과 데이터가 어긋날 수 있으니, 갱신 시 현재 페이지를 .now로 리셋하거나 현재 페이지 기준으로 다시 조회하도록 맞춰야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`
around lines 44 - 49, Update action(input:) for .invalidateCacheAndRefetch so
EmotionCalendarView.currentPage is synchronized with the refresh: reset the
calendar page to .now before fetching, or use the current page as the date for
fetchMonthlyBadge and fetchEmotionHistory. Ensure the displayed month and
refreshed data always match when the reused view reappears.
| var emotionHistoryRecords: [String: EmotionMarble] = [:] | ||
| for emotionHistory in emotionHistoryEntities { | ||
| guard let marble = Marble(rawValue: emotionHistory.emotionMarbleType) | ||
| else { return } | ||
|
|
||
| let emotionMarble = EmotionMarble(marble: marble, imageUrl: emotionHistory.imageUrl) | ||
| emotionHistoryRecords[emotionHistory.date] = emotionMarble | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
알 수 없는 emotionMarbleType 하나가 전체 달의 감정 기록 조회를 무효화합니다.
for 루프 안의 guard ... else { return }는 루프 반복을 건너뛰는 게 아니라 Task 클로저 전체를 종료시킵니다. 서버가 클라이언트가 아직 모르는 emotionMarbleType을 하나라도 반환하면, 이미 처리된 나머지 레코드까지 모두 버려지고 캐시 저장(emotionHistoryCache[key] = ...)과 monthlyEmotionHistorySubject.send(...)가 전부 스킵됩니다. 결과적으로 해당 월의 캘린더가 통째로 비어 보이게 됩니다.
🐛 제안: 개별 레코드만 스킵
var emotionHistoryRecords: [String: EmotionMarble] = [:]
for emotionHistory in emotionHistoryEntities {
guard let marble = Marble(rawValue: emotionHistory.emotionMarbleType)
- else { return }
+ else {
+ BitnagilLogger.log(logType: .error, message: "알 수 없는 emotionMarbleType: \(emotionHistory.emotionMarbleType)")
+ continue
+ }📝 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 emotionHistoryRecords: [String: EmotionMarble] = [:] | |
| for emotionHistory in emotionHistoryEntities { | |
| guard let marble = Marble(rawValue: emotionHistory.emotionMarbleType) | |
| else { return } | |
| let emotionMarble = EmotionMarble(marble: marble, imageUrl: emotionHistory.imageUrl) | |
| emotionHistoryRecords[emotionHistory.date] = emotionMarble | |
| } | |
| var emotionHistoryRecords: [String: EmotionMarble] = [:] | |
| for emotionHistory in emotionHistoryEntities { | |
| guard let marble = Marble(rawValue: emotionHistory.emotionMarbleType) | |
| else { | |
| BitnagilLogger.log(logType: .error, message: "알 수 없는 emotionMarbleType: \(emotionHistory.emotionMarbleType)") | |
| continue | |
| } | |
| let emotionMarble = EmotionMarble(marble: marble, imageUrl: emotionHistory.imageUrl) | |
| emotionHistoryRecords[emotionHistory.date] = emotionMarble | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@Projects/Presentation/Sources/ActivityHistory/ViewModel/ActivityHistoryViewModel.swift`
around lines 93 - 100, Update the loop building emotionHistoryRecords so an
unrecognized emotionMarbleType skips only that individual emotionHistory record,
rather than returning from the surrounding Task closure. Preserve processing of
all valid records and allow the subsequent emotionHistoryCache update and
monthlyEmotionHistorySubject.send call to execute.
taipaise
left a comment
There was a problem hiding this comment.
캐싱까지 구현하시다니 좋와요~~ 고생하셨습니다!!
🌁 Background
활동 기록 관련 API 로직을 구현했어요 ~
📱 Screenshot
👩💻 Contents
ActivityHistoryRepository)📝 Review Note
1.
Marble과EmotionMarble기존 Marble enum이 있지만 서버에서 받은 응답 값인 imageUrl을 저장하기 위해 EmotionMarble 구조체를 따로 만들었습니다.
2. 감정 구슬 기록 캐싱
ActivityHistoryViewModel에서monthKey(yyyy-MM)를 기준으로 월별 감정 기록을 캐싱했습니다.사용자가 같은 달을 다시 조회하거나 달력을 이동할 때 이미 조회한 데이터라면 API를 재호출하지 않고 캐시된 데이터를 사용하도록 하여 불필요한 네트워크 요청을 줄였습니다.
monthKey가
yyyy-MM형식으로 이루어져 있지만, yyyy년 MM월에 해당하는 감정 구슬 기록 조회 요청을 할 때MM 이전 달의 6일, MM 이후 달의 6일을 추가적으로 더 불러오도록 하였습니다.
Summary by CodeRabbit