---
id: ios-core-data-patterns
title: Core Data — Context / Thread / Migration
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [ios, coredata, persistence, vibe-coding]
tech_stack: { language: "Swift / Core Data", applicable_to: ["iOS", "macOS"] }
applied_in: []
aliases: [NSManagedObjectContext, persistent container, lightweight migration]
---

# Core Data 실전 패턴

> Apple 의 ORM. **Context 는 thread-bound** — 다른 thread 에서 같은 context 쓰면 crash. background context + view context 분리, save 전파, lightweight migration 가 표준.

## 📖 핵심 개념
- `NSPersistentContainer`: 스택 부팅.
- `viewContext`: main thread 만. UI 표시.
- `newBackgroundContext()`: 백그라운드 변경.
- Lightweight migration: 자동 schema migration (필드 추가 등).

## 💻 코드 패턴

### Stack 부팅
```swift
final class CoreDataStack {
    static let shared = CoreDataStack()
    let container: NSPersistentContainer

    private init() {
        container = NSPersistentContainer(name: "Model")
        container.loadPersistentStores { (desc, error) in
            if let error = error { fatalError("Core Data init failed: \(error)") }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
    }

    func save() {
        let ctx = container.viewContext
        if ctx.hasChanges {
            do { try ctx.save() } catch { print("save error: \(error)") }
        }
    }
}
```

### Background write
```swift
container.performBackgroundTask { ctx in
    let entity = User(context: ctx)
    entity.id = UUID()
    entity.name = "Alice"
    do { try ctx.save() } catch { print(error) }
    // viewContext 가 자동 merge
}
```

### NSFetchedResultsController + SwiftUI
```swift
struct UserList: View {
    @FetchRequest(sortDescriptors: [SortDescriptor(\User.name)])
    private var users: FetchedResults<User>

    var body: some View {
        List(users) { u in Text(u.name ?? "") }
    }
}
```

### Predicate
```swift
let req: NSFetchRequest<Order> = Order.fetchRequest()
req.predicate = NSPredicate(format: "userId == %@ AND status IN %@", userId as CVarArg, ["pending","paid"])
req.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
req.fetchLimit = 50
let results = try ctx.fetch(req)
```

### Lightweight migration
```swift
let desc = NSPersistentStoreDescription(url: storeURL)
desc.shouldMigrateStoreAutomatically = true
desc.shouldInferMappingModelAutomatically = true
container.persistentStoreDescriptions = [desc]
```

## 🤔 의사결정 기준
| 상황 | 권장 |
|---|---|
| 작은 앱 | Core Data 또는 SQLite (GRDB) |
| 큰 앱 + 동기화 | Core Data + CloudKit, 또는 Realm |
| 매우 복잡 / 성능 critical | GRDB / SQLite 직접 |
| 신규 앱 | SwiftData (iOS 17+) — Core Data 의 Swift-native wrapper |
| Multi-platform sync | CloudKit / Firebase / Supabase |

## ❌ 안티패턴
- **viewContext 를 background 에서 사용**: crash. background context 사용 + perform/performAndWait.
- **NSManagedObject 를 thread 간 전달**: objectID 만 전달, 다른 context 에서 fetch.
- **save 안 함**: app terminate 시 손실. 적절 시점 save.
- **거대 fetch**: 메모리 폭발. fetchLimit / fetchBatchSize.
- **thread-confinement 무시**: perform { } 블록 안에서만.
- **schema 큰 변경 없이 lightweight 시도**: 마이그레이션 실패. mapping model 명시.
- **delete propagation 안 설정**: orphan rows.

## 🤖 LLM 활용 힌트
- 신규 = SwiftData (iOS 17+). 그 외 Core Data.
- viewContext = main, background = perform.

## 🔗 관련 문서
- [[iOS_Swift_Concurrency_async_await]]
- [[iOS_Keychain_Storage]]
