본문 바로가기
감정일기(가칭)

💾 왜 Core Data의 Read는 반드시 async여야 할까?

by 밤새는 탐험가89 2025. 10. 18.
728x90
SMALL

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에서 수행

728x90
LIST