Core Data는 로컬 데이터베이스지만,
“로컬이니까 동기로 처리해도 된다”는 생각은 큰 착각이에요
특히 인스타그램이나 포토앨범처럼
많은 이미지가 컬렉션뷰에 동시에 보여지는 앱이라면
비동기(async/await)를 적용하지 않으면 UI가 끊기거나 멈출 수 있습니다.
🧩 1️⃣ Core Data는 로컬 DB지만 I/O는 여전히 무겁다
Core Data 내부적으로는 SQLite를 사용합니다.
즉, fetchRequest는 결국 디스크 I/O 연산이에요.
여기에 “이미지 파일 로드”까지 합쳐지면 이렇게 됩니다 👇
Core Data fetch → FileManager 이미지 경로 접근 →
UIImage 디코딩 → 메모리 로드 → 화면 표시
이 모든 과정이 메인 스레드에서 동기로 수행된다면,
당연히 UI 렌더링은 멈춰버려요.
결과적으로 스크롤이 끊기거나 “하얀 화면”이 잠깐 보이게 됩니다.
📸 2️⃣ 인스타그램형 UI에서 특히 더 문제다
예를 들어 감정일기 앱에서
하나의 화면에 30개의 썸네일 이미지를 보여준다고 해볼게요.
func fetchDiaries(mode: FetchMode) -> [EmotionDiaryModel] {
let request: NSFetchRequest<EmotionDiaryEntity> = EmotionDiaryEntity.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
request.sortDescriptors = [sortDescriptor]
do {
let entities = try context.fetch(request)
return entities.compactMap { $0.toModel() } // 내부에서 이미지 로드
} catch {
return []
}
}
이 코드가 메인 스레드에서 실행되면,
Core Data + FileManager + UIImage 디코딩이 모두 한 번에 몰리게 됩니다.
➡️ 결과: 화면 멈춤, 스크롤 끊김, 렌더링 지연
⚙️ 3️⃣ 해결책 — “동기 + async 병행 구조”
Core Data 매니저 내부에서는 기존 동기 코드 유지,
UI나 ViewModel에서는 비동기로 감싸서 호출하는 구조가 가장 좋아요.
✅ 예시: CoreDataManager 내부
// 기존 동기 버전 (내부 테스트나 단순 호출용)
func fetchDiaries(mode: FetchMode) -> [EmotionDiaryModel] {
...
}
// 비동기 버전 (UI에서 사용)
func fetchDiariesAsync(mode: FetchMode) async -> [EmotionDiaryModel] {
await withCheckedContinuation { continuation in
DispatchQueue.global(qos: .userInitiated).async {
let result = self.fetchDiaries(mode: mode)
continuation.resume(returning: result)
}
}
}
✅ ViewModel에서 호출
@MainActor
func loadDiaries() async {
isLoading = true
diaries = await coreDataManager.fetchDiariesAsync(mode: .paged(limit: 20, offset: 0))
isLoading = false
}
➡️ 이렇게 하면:
1. UI는 절대 멈추지 않고,
2. Core Data I/O는 백그라운드에서 수행되며,
3. 데이터가 준비되면 UI가 자연스럽게 업데이트됩니다.
🧠 4️⃣ 왜 Create / Update / Delete는 동기로 충분한가?
| 구분 | 비동기 필요성 | 이유 |
| Create | ⭕ 선택적 | 보통 한 건 단위 저장, 잠깐 대기 가능 |
| Update | ⭕ 선택적 | 단건 수정, UI 블로킹 영향 적음 |
| Delete | ⭕ 선택적 | 단건 삭제, 사용자 인식에 영향 없음 |
| Read (특히 다건 fetch) | ✅ 필수 | I/O + 이미지 디코딩이 집중됨 |
“Create/Update/Delete는 사람이 기다려줄 수 있는 작업이지만,
Read는 화면을 여는 순간 바로 보여야 하니까
비동기로 돌리지 않으면 프레임이 떨어진다.”
💡 5️⃣ 핵심 설계 포인트 요약
| 포인트 | 설명 |
| ✅ Core Data의 fetch는 SQLite 접근 | 로컬이지만 디스크 I/O 연산이라 느릴 수 있음 |
| ✅ FileManager + UIImage 디코딩 병목 | Read 시 가장 큰 프레임 저하 원인 |
| ✅ 비동기 fetch로 UI 프리즈 방지 | 백그라운드 큐에서 로드 후 MainActor로 UI 갱신 |
| ✅ 동기 + async 병행 구조 추천 | 테스트·내부 코드 단순화 + UI 반응성 확보 |
🧭 결론
Core Data의 Read는 “로컬”이라도 절대 가볍지 않다.
특히 이미지, 썸네일, 그리드 레이아웃을 다룬다면
비동기(async/await)로 감싸서 UI 프리즈를 막는 게 필수다.
👉 정리하자면:
1. CRUD 중 Read만큼은 반드시 async
2. DispatchQueue.global 또는 Task로 백그라운드에서 처리
3. UI 업데이트는 @MainActor에서 수행
'감정일기(가칭)' 카테고리의 다른 글
| 🚀 Swift 6에서 바뀐 Concurrency 규칙 완전 정리 (0) | 2025.10.19 |
|---|---|
| 🧠 Swift Concurrency — “Capture of 'self' with non-sendable type” 완전 정리 (1) | 2025.10.19 |
| 📚 DiaryCoreDataManager 내부 구조 완전 정리 (0) | 2025.10.18 |
| ☁️ Core Data에서 Firebase로 확장하는 iOS 데이터 구조 설계 (0) | 2025.10.18 |
| 🧩 Core Data 매니저 네이밍, 어떻게 하는 게 좋을까? (0) | 2025.10.18 |