LemonLog에서 Store API를 설계한 이유
LemonLog에서는 감정일기를 CoreData에 저장하고,
DiaryStore를 통해 ViewModel이 데이터를 접근하도록 설계했다.
이 과정에서 다음 두 가지 API가 자연스럽게 등장했다.
var diariesPublisher: AnyPublisher<[EmotionDiaryModel], Never>
func fetchDiaryByDay(from date: Date) -> [EmotionDiaryModel]
처음에는 이런 의문이 들었다.
“이미 diariesPublisher가 전체 감정일기를 계속 송출하고 있는데,
굳이 날짜 기반으로 조회하는 fetchDiaryByDay가 또 필요할까?”
이 글은 그 질문에 대한 답을 정리한 것이다.
1️⃣ diariesPublisher는 무엇을 위한 API인가?
diariesPublisher는 Store가 가진 전체 감정일기의 변화 스트림이다.
- 새 일기 작성
- 기존 일기 수정
- 일기 삭제
이 중 어떤 일이 발생하든,
Store는 변경된 전체 일기 배열을 다시 송출한다.
store.diariesPublisher
.sink { diaries in
// 항상 최신 상태의 전체 일기
}
핵심 포인트
diariesPublisher는 “확인용”이 아니라
“화면을 계속 최신 상태로 유지하기 위한 스트림”이다.
2️⃣ 날짜별 일기 화면에서 diariesPublisher를 사용하는 이유
날짜를 선택했을 때 보여주는 “일기 리스트 화면”은
반응형 UI가 필요한 대표적인 화면이다.
- 다른 화면에서 일기를 삭제해도
- 편집 후 돌아와도
- 새로 작성해도
👉 리스트 화면은 자동으로 최신 상태를 반영해야 한다.
그래서 DiaryListViewModel에서는
diariesPublisher를 구독하고, 날짜만 필터링한다.
store.diariesPublisher
.map { diaries in
diaries.filter { $0.createdAt.isSameDay(as: selectedDate) }
}
.assign(to: &$diaries)
이 구조에서는:
- 추가 fetch ❌
- 재요청 ❌
- 수동 갱신 ❌
Publisher 하나로 화면 상태가 완전히 유지된다.
3️⃣ 그렇다면 fetchDiaryByDay(from:)는 필요 없는가?
결론부터 말하면:
❌ 리스트 화면에서는 필요 없다
✅ 앱 전체 구조에서는 역할이 다르다
fetchDiaryByDay(from:)는 Reactive API가 아니다.
이 함수는 Query(조회) API다.
4️⃣ 두 API의 역할 차이
| 구분 | diariesPublisher | fetchDiaryByDay(from:) |
| 성격 | Reactive Stream | Synchronous Query |
| 자동 갱신 | ✅ | ❌ |
| 사용 대상 | 화면(ViewModel) | 판단 / 계산 로직 |
| 변경 반영 | 자동 | 수동 재호출 필요 |
| 목적 | UI 상태 유지 | 조건 확인 |
5️⃣ fetchDiaryByDay(from:)는 언제 쓰는가?
✅ 1. 즉시 판단이 필요한 경우
func hasDiary(on date: Date) -> Bool {
!store.fetchDiaryByDay(from: date).isEmpty
}
- 달력에서 점 표시 여부
- “이 날 일기 있음 / 없음” 판단
- Combine 스트림이 필요 없는 로직
✅ 2. 단발성 조회가 필요한 경우
- 앱 시작 시 오늘 일기 존재 여부 확인
- 특정 날짜에 대한 조건 계산
✅ 3. 테스트 코드
let result = store.fetchDiaryByDay(from: date)
XCTAssertEqual(result.count, 2)
Publisher 없이도
명확하고 단순한 테스트가 가능하다.
6️⃣ 자주 하는 오해 (중요)
❌ “수정 / 삭제 / 생성 시 확실하게 하기 위해
fetchDiaryByDay를 다시 호출해야 한다”
이건 잘못된 이해다.
- 수정 / 삭제 / 생성 발생
- Store 내부 데이터 변경
- diariesPublisher가 자동으로 새 값을 송출
- ViewModel이 즉시 반영
👉 Reactive 화면에서는 fetch 함수가 필요 없다.
7️⃣ LemonLog에서 선택한 기준
📌 화면(ViewModel)
- 항상 diariesPublisher 사용
- 화면 상태를 반응형으로 유지
📌 판단 / 계산 / 유무 체크
- fetchDiaryByDay(from:) 사용
- 단순하고 명확한 조회
8️⃣ 이 설계의 핵심 원칙
Store는 “데이터 접근 방법”을 제공하고
ViewModel은 “화면에 맞는 데이터 형태”를 만든다
- Store는 똑똑해지지 않는다
- ViewModel이 해석한다
- 화면은 자동으로 갱신된다
마무리
처음에는
“API가 중복된 것 아닌가?”라는 의문에서 시작했지만,
정리해보니 두 API는 경쟁 관계가 아니라 역할 분담이었다.
- diariesPublisher → 반응형 UI를 위한 스트림
- fetchDiaryByDay(from:) → 즉시 판단을 위한 조회
이 기준을 세우고 나니,
Store와 ViewModel의 경계가 훨씬 명확해졌다.
'감정일기(가칭)' 카테고리의 다른 글
| Combine의 .assign vs .sink (0) | 2025.12.16 |
|---|---|
| LemonLog: isSameDay와 bind()로 날짜별 일기 리스트 만들기 (Combine + @MainActor) (0) | 2025.12.16 |
| 📅 날짜 선택 → 감정일기 리스트 화면 설계하기 (LemonLog 사례) (0) | 2025.12.16 |
| 📱 UICollectionView 내부 셀의 날짜 갱신이 안 되는 이유와 해결 방법— 특히 “셀 자체를 전달하는 방식”의 강력함 (0) | 2025.12.10 |
| 📘 감정일기 작성 화면에서 날짜 선택 UI는 어떻게 설계해야 할까?— 달력을 직접 넣을까, 시트로 띄울까? 구조적 고민과 결론 정리 (0) | 2025.12.10 |