iOS 개발을 하다 보면 처음엔 Core Data로만 시작했다가
나중엔 데이터를 클라우드에 동기화하고 싶어지는 순간이 옵니다.
저도 처음엔 단순히 감정일기를 저장하는 로컬 앱이었는데,
점점 데이터를 백업하거나 여러 기기에서 동기화하고 싶어졌어요.
그래서 이번 글에서는,
“Core Data로 시작해서 Firebase로 확장하는 구조 설계”
에 대해 실제 구현 흐름과 함께 정리해봤습니다 💡
🧭 1️⃣ 초기 구조 — Core Data 중심의 앱 설계
초기 앱 버전에서는 “로컬 데이터만 잘 저장하고 관리”하는 게 핵심이에요.
즉, 네트워크 동기화보다 안정적인 오프라인 구조가 우선이죠.
이 단계에서 가장 좋은 접근은 바로 👇
final class DiaryCoreDataManager {
static let shared = DiaryCoreDataManager()
private init() {}
func saveDiary(_ model: EmotionDiaryModel) { ... }
func fetchDiaries(mode: FetchMode) -> [EmotionDiaryEntity] { ... }
func updateDiary(_ model: EmotionDiaryModel) { ... }
func deleteDiary(_ diary: EmotionDiaryEntity) { ... }
}
✅ 이 구조의 장점
- Core Data Context를 직접 관리
- 빠르고 단순한 CRUD 구현
- FileManager와 연동하여 이미지 경로만 저장
즉, “작고 빠른 MVP 앱”을 만들 때 가장 적합한 구조입니다.
⚙️ 2️⃣ 앱이 커지면 생기는 문제
앱 버전이 올라가고 데이터가 쌓이기 시작하면, 이런 고민이 생깁니다.
“이 데이터를 백업하거나, 다른 기기에서도 보고 싶다.”
“Firebase를 붙이면 될 것 같은데, Core Data 코드 다 갈아엎어야 하나…?”
이 시점에서 문제는 저장소가 늘어난다는 것이에요.
| 저장소 | 특징 | 예시 |
| 🗂️ Core Data | 오프라인 접근, 빠른 로컬 캐시 | 감정일기 저장 |
| ☁️ Firebase | 클라우드 백업, 실시간 동기화 | 사용자 계정 기반 저장 |
결국 Core Data와 Firebase를 둘 다 관리할 수 있는 구조가 필요해집니다.
🧱 3️⃣ 전환의 핵심 — Repository 패턴
이때 등장하는 개념이 바로 Repository 패턴이에요.
쉽게 말하면,
“ViewModel이 데이터가 어디서 오든 신경 쓰지 않게 하는 중간층”
입니다.
🗃️ Step 1: Repository 프로토콜 정의
protocol DiaryRepository {
func saveDiary(_ model: EmotionDiaryModel)
func fetchDiaries() -> [EmotionDiaryModel]
func deleteDiary(_ id: UUID)
}
이제 ViewModel은 “어디서 데이터를 가져오든” 상관없이
DiaryRepository만 호출하면 됩니다 👇
final class DiaryListViewModel {
private let repository: DiaryRepository
init(repository: DiaryRepository) {
self.repository = repository
}
func load() {
let diaries = repository.fetchDiaries()
print("불러온 일기:", diaries.count)
}
}
🗂️ Step 2: Local Repository (Core Data 기반)
final class LocalDiaryRepository: DiaryRepository {
private let coreDataManager = DiaryCoreDataManager.shared
func saveDiary(_ model: EmotionDiaryModel) {
coreDataManager.saveDiary(model)
}
func fetchDiaries() -> [EmotionDiaryModel] {
coreDataManager.fetchDiaries(mode: .all).compactMap { $0.toModel() }
}
func deleteDiary(_ id: UUID) {
if let diary = coreDataManager.fetchDiaries(mode: .all)
.first(where: { $0.id == id.uuidString }) {
coreDataManager.deleteDiary(diary)
}
}
}
이 구조는 지금까지의 Core Data 코드를 그대로 재사용합니다.
즉, 리팩토링 없이 구조만 깔끔히 감쌉니다.
☁️ Step 3: Remote Repository (Firebase 기반)
final class RemoteDiaryRepository: DiaryRepository {
func saveDiary(_ model: EmotionDiaryModel) {
// Firebase Firestore에 업로드
}
func fetchDiaries() -> [EmotionDiaryModel] {
// Firestore 데이터 불러오기
return []
}
func deleteDiary(_ id: UUID) {
// Firebase에서 문서 삭제
}
}
나중에 Firebase를 붙이면
Core Data와 완전히 다른 방식으로 데이터를 저장하지만,
ViewModel은 여전히 repository.saveDiary()만 호출하면 됩니다 🙌
🔗 Step 4: 통합 Repository (로컬 + 리모트)
나중에는 두 저장소를 동시에 다룰 수도 있습니다.
예를 들어:
final class CombinedDiaryRepository: DiaryRepository {
private let localRepo = LocalDiaryRepository()
private let remoteRepo = RemoteDiaryRepository()
func saveDiary(_ model: EmotionDiaryModel) {
localRepo.saveDiary(model)
remoteRepo.saveDiary(model)
}
func fetchDiaries() -> [EmotionDiaryModel] {
let local = localRepo.fetchDiaries()
let remote = remoteRepo.fetchDiaries()
return (local + remote).sorted { $0.createdAt > $1.createdAt }
}
func deleteDiary(_ id: UUID) {
localRepo.deleteDiary(id)
remoteRepo.deleteDiary(id)
}
}
이렇게 하면 앱은 오프라인일 땐 Core Data,
온라인일 땐 Firebase와 자동 동기화되도록 확장할 수 있습니다.
🧩 4️⃣ 단계별 성장 로드맵 요약
| 앱 버전 | 데이터 저장소 | 클래스 이름 | 구조 특징 |
| 🚀 v1.0~v2.0 | Core Data | DiaryCoreDataManager | 로컬 CRUD, 빠른 MVP 구현 |
| ☁️ v3.0 | Core Data + Firebase | DiaryRepository | 데이터 소스 추상화, Firebase 확장 |
| 🔄 v4.0 이후 | Firebase + Core Data | CombinedDiaryRepository | 동기화 및 캐시 처리 완성형 |
⚙️ 구조 다이어그램 (요약)
┌───────────────────────────────┐
│ ViewModel │
│ (DiaryListViewModel 등) │
└──────────────┬────────────────┘
│
▼
┌────────────────────┐
│ DiaryRepository │ ← 프로토콜
└────────────────────┘
▲ ▲
│ │
┌────────────────────┐ ┌────────────────────┐
│ LocalDiaryRepo │ │ RemoteDiaryRepo │
│ (Core Data) │ │ (Firebase) │
└────────────────────┘ └────────────────────┘
💬 마무리 — “하나의 구조로 두 세계를 잇다”
처음부터 완벽한 Firebase 동기화를 목표로 하면
개발 속도도, 코드 복잡도도 두 배로 늘어납니다.
그래서 현실적인 전략은 이거예요 👇
1️⃣ Core Data로 빠르고 안정적으로 MVP 완성
2️⃣ Firebase를 도입할 때 Repository 패턴으로 추상화
3️⃣ 나중에 로컬/리모트 통합 구조로 확장
이렇게 하면 코드를 갈아엎지 않고,
한 단계씩 자연스럽게 성장하는 앱 구조를 만들 수 있습니다 🌱
'감정일기(가칭)' 카테고리의 다른 글
| 💾 왜 Core Data의 Read는 반드시 async여야 할까? (0) | 2025.10.18 |
|---|---|
| 📚 DiaryCoreDataManager 내부 구조 완전 정리 (0) | 2025.10.18 |
| 🧩 Core Data 매니저 네이밍, 어떻게 하는 게 좋을까? (0) | 2025.10.18 |
| 📒 감정일기 앱의 데이터 로딩 전략 설계 — 동기 로딩 vs 점진적 로딩(Pagination) (0) | 2025.10.17 |
| 🚀 현실적인 iOS 데이터 로딩 전략 — Core Data와 Pagination(페이징) 이야기 (0) | 2025.10.17 |