[Swift] CoreData 알아보기
CoreData란?
“애플이 제공하는 In-memory 방식의 데이터 관리 프레임워크”
- Core Data는 DB처럼 보이지만 프레임워크
- 실질적으로는 앱 메모리 안에서 객체를 관리하고 추적하는 방식으로 동작
- 디스크 저장 여부는 백엔드 스토어 설정에 따라 다름
Q1. UserDefaults와 차이점?
UserDefaults는 간단한 정보를 저장하기에 적합하다면,
CoreData는 복잡하고 큰 user data를 저장하기에 적합하다.
Q2. Database인가?
아니다. Framework이다.
넓은 의미로 앱의 모델 계층이며, 객체 그래프를 관리하는 프레임워크이다.
객체 그래프를 디스크에 저장하여 Persistence(영구적으로 저장) 기능을 이용한다.
관계형 데이터베이스인 SQLite에 의해 영구적 저장 기능이 지원된다.
꼭 Persistence 기능을 사용하지 않고도 CoreData를 사용할 수 있다.
CoreData 구성 요소
1. NSManagedObjectModel (모델 도면)
📐 데이터 구조 설계도
- 어떤 엔티티(테이블)들이 있고
- 어떤 속성(컬럼)들이 있으며
- 관계는 어떻게 되어 있는지를 정의하는 스키마
- ✅ .xcdatamodeld 파일에 해당!
2. NSManagedObject
- Core Data에서 데이터를 표현하는 기본 클래스, 객체 인스턴스
- 데이터베이스(=Core Data의 persistent store)에 저장되는 객체를 표현
- 데이터 모델(.xcdatamodeld)에 정의된 Entity(테이블)**와 속성(attribute)들을 런타임에 동적으로 처리
- SQL 테이블의 한 행(row) 같은 개념입니다. 하나의 레코드(객체화된 행)
- Core Data 모델에 Person이라는 Entity가 있고, name, age라는 속성이 있다면
- class Person: NSManagedObject { @NSManaged var name: String? @NSManaged var age: Int16 }
- 이 Person 클래스는 Core Data의 NSManagedObject를 상속받는다.
- 각각의 속성은 @NSManaged 키워드를 통해 Core Data가 런타임에 속성 정보를 연결한다.
- 기본적으로 제공되는 타입이 아닌 커스텀한 타입을 사용하고자 한다면 Transformable 타입을 선택하고 우측 인스펙터 영역에서 Custom Class를 지정하여 리스트에 노출되는 기본적인 타입 외 다른 커스텀한 타입을 사용할 수 있다.
@NSManaged
Core Data가 속성의 저장과 관리를 자동으로 처리할 수 있도록 마킹하는 키워드, property attribute
- 이 속성(name, age)은 내가 직접 구현하지 않겠다고 명시
- 대신 Core Data 런타임이 자동으로 getter/setter를 생성하고 관리하도록 맡긴다는 뜻
- NSManagedObject의 서브클래스에서 Core Data 모델에 정의된 속성이나 관계를 선언할 때 사용
- NSManagedObject는 Objective-C 기반의 Key-Value Coding (KVC)와 Key-Value Observing (KVO)을 활용합니다.
- 즉, 이 속성들은 컴파일 타임이 아닌 런타임에 동적으로 구현됩니다.
- @NSManaged 속성은 초기화하면 안 된다!
- @NSManaged var name: String // Core Data가 값을 넣어줌
- 내부적으로 NSManagedObjectContext와 연동됨
- → 수정 시 context가 추적함
- → 저장 시 자동으로 Persistent Store에 기록됨
3. NSManagedObjectContext (작업 현장, 메모리 캐시)
🛠️ 작업을 수행하는 메모리 공간 (임시 저장소) Core Data에서 데이터를 생성, 조회, 수정, 삭제하고 추적하는 작업 공간
[생성]
Core Data는 일반적으로 이 구조를 통해 context를 제공:
let context = NSPersistentContainer(name: "Model").viewContext
또는 앱에서 주입된 CoreDataManager.shared.context 같은 형태로 사용합니다.
[조회]
NSFetchRequest
데이터를 "조회(fetch)"할 때 반드시 필요함
Core Data에서 어떤 엔티티의 어떤 데이터를 조회할지 정의하는 질의 객체(query object)
NSPredicate: 조건 (WHERE 절처럼 동작)
[삭제]
context.delete(object)
try context.save()
// 예시
let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %@", "민지")
do {
let users = try context.fetch(fetchRequest)
if let userToDelete = users.first {
context.delete(userToDelete)
try context.save()
print("🗑 삭제 완료!")
}
} catch {
print("❌ 삭제 중 에러 발생: \\(error)")
}
fetch하여 얻은 데이터를 수정하거나 삭제한 이후에 context.save()를 해야만 반영이 된다!
4. NSPersistentStore (진짜 저장소, 디스크)
💾 데이터가 실제로 저장되는 공간
- SQLite / In-memory / Binary 등 설정 가능
- NSInMemoryStoreType, NSSQLiteStoreType, NSBinaryStoreType
클래스역할 요약
NSPersistentContainer | 🧠 Core Data를 쉽게 다룰 수 있게 해주는 통합 관리자 |
NSPersistentStoreCoordinator | 🧩 Context와 실제 저장소(Store)를 연결시켜주는 중간 관리자 |
NSPersistentStore | 💾 실제 데이터가 저장되는 장소 (SQLite, In-memory 등) |
NSPersistentContainer
- 내부적으로 다음 모두를 생성해줌:
- NSManagedObjectModel
- NSPersistentStoreCoordinator
- NSPersistentStore
- NSManagedObjectContext
let container = NSPersistentContainer(name: "Model")
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType // 🔹 In-memory 저장소
container.persistentStoreDescriptions = [description]
**container.loadPersistentStores** { _, error in
if let error = error {
fatalError("❌ Store error: \\(error)")
}
}
let context = container.viewContext
let user = User(context: context)
user.name = "Minji"
// 변경됨! ✅
// 하지만 아직 메모리에도 commit되지 않았음
try? context.save() // ⬅️ 여전히 필수
- 개발자는 context와 loadPersistentStores만 신경 쓰면 됨
- loadPersistentStores()는 다음을 수행합니다:
- persistentStoreDescriptions에 등록된 저장소 설정을 순회
- 각 저장소의 경로에 SQLite 파일이 없으면 → 새로 생성
- NSPersistentStoreCoordinator를 통해 저장소 등록
- 이후 context가 save()할 때 이 저장소로 데이터가 반영됨
- loadPersistentStores()는 다음을 수행합니다:
[CoreData 흐름]
NSManagedObjectContext.save()
⬇
NSPersistentStoreCoordinator
⬇
NSPersistentStore (ex. SQLite)
- context에 등록된 NSManagedObject들의 변경 사항을 추적 중
- save()를 호출하면 context는 변경된 오브젝트 목록(dirty list) 을 coordinator에게 전달
- coordinator는 해당 NSPersistentStore에 기록하라고 지시
- store는 타입에 따라 (SQLite라면) .sqlite 파일에 SQL 실행
- In-memory라면 RAM에 그대로 유지
CoreData model 만들기
- .xcdatamodeld file을 추가한다.
- File > New > File > Core Data section > Data Model
- Entity 추가ex) Contact
- Entity란 저장되고 관리되어야 하는 데이터의 집합
- Entity 하위에 Attribute 추가
- ex) name, phoneNumber
Generating code
자동으로 두 파일을 generate (class, properties file)
- The class file declares the class as a subclass of NSManagedObject
Xcode에서 Core Data Entity를 정의하면, 그에 대응되는 NSManagedObject 서브클래스 파일을 자동 생성할 수 있습니다.
Core Data의 .xcdatamodeld 파일에서 정의한 Entity들을 기반으로,
Xcode가 자동으로 대응되는 Swift 클래스를 생성해주는 기능입니다.
이 클래스를 생성하면:
- Core Data의 속성(attribute), 관계(relationship)가 코드로 매핑되고
- 해당 Entity를 다룰 때 안전하게 타입을 사용할 수 있게 됩니다
// Person+CoreDataClass.swift
import Foundation
import CoreData
@objc(Person)
public class Person: NSManagedObject {
}
// Person+CoreDataProperties.swift
import Foundation
import CoreData
extension Person {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Person> {
return NSFetchRequest<Person>(entityName: "Person")
}
@NSManaged public var name: String?
@NSManaged public var age: Int16
}
- Class.swift - NSManagedObject를 상속하는 실제 클래스 선언
- Properties.swift - 속성과 fetchRequest() 확장 정의
이렇게 분리된 이유는 보통 Properties 확장은 재생성 가능하고,
Class는 개발자가 커스텀 로직을 넣기 위해 유지되기 때문입니다.
- 자동 생성된 파일을 수정하지 않고 유지하면,
- 모델 변경 시 Properties.swift만 다시 생성하면 됩니다.
- Entity 이름은 클래스 이름과 정확히 일치해야 하며,
- Xcode가 @objc(EntityName)으로 Objective-C 런타임 연결도 해줍니다.
- 모델에 변경이 생겼는데 자동 생성 파일을 재생성하지 않으면,
- 코드와 모델 사이에 싱크가 어긋날 수 있음
- 새 속성을 추가했다면 Editor > Create NSManagedObject Subclass...를 다시 실행하거나,
- 수동으로 속성을 추가해야 합니다
SwiftData와 비교
Core Data를 대체하는 Swift 전용 데이터 저장 프레임워크
SwiftData는 Apple이 WWDC 2023에서 발표한, Core Data의 현대적인 대체 기술이다.
복잡했던 Core Data의 선언 방식과 클래스를 훨씬 간단하고 선언적으로 바꿔준다.
SwiftData는 Apple이 만든 Swift-native 데이터 영속성 프레임워크이다.
특징
완전 Swift 기반 | Swift 문법과 타입 시스템에 최적화됨 |
선언형 모델 정의 | @Model 키워드로 struct나 class를 간단하게 선언 |
더 간결한 코드 | 별도 NSManagedObject 서브클래스나 @NSManaged 필요 없음 |
SwiftUI와 궁합 좋음 | @Query, @Environment(\\.modelContext) 등으로 뷰에서 쉽게 사용 가능 |
@Model
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
- @Model만 붙이면 끝!
- SwiftData가 자동으로 저장, 변경 추적, 관계 관리 등을 처리
Core Data vs SwiftData
모델 정의 | .xcdatamodeld + NSManagedObject 서브클래스 | @Model 붙인 Swift 클래스 |
속성 선언 | @NSManaged 필요 | 그냥 Swift 속성으로 선언 |
CRUD 방식 | Context 직접 다루고 fetch 요청 따로 작성 | SwiftUI 스타일로 @Query, .modelContext 사용 |
관계 설정 | 수동 설정 (to-one, to-many) | Swift 타입으로 자연스럽게 표현 가능 |
SwiftUI 통합 | 번거로움 | 완전 통합됨 (예: @Query, .modelContext.insert) |
@Model
class Task {
var title: String
var isDone: Bool = false
}
struct TaskListView: View {
@Query var tasks: [Task]
@Environment(\\.modelContext) var context
var body: some View {
List {
ForEach(tasks) { task in
Text(task.title)
}
}
Button("Add Task") {
let newTask = Task(title: "New Task")
context.insert(newTask)
}
}
}
- iOS 17 / macOS 14 이상에서만 사용 가능
- 아직 Core Data만큼의 기능 확장성은 부족 (예: 복잡한 fetch, migration 등)
여러 모델 버전이 있을 때 버전 지정하기
기본 버전 지정:
- .xccurrentversion 파일에서 현재 사용할 모델 버전을 지정
- xml<string>1.11.0.xcdatamodel</string>
- <key>_XCCurrentVersionName</key>
모델 관리
.xcdatamodeld | Core Data 모델의 “버전 모음 폴더” (버전 여러 개 포함 가능) |
.xcdatamodel | 실제 각각의 모델 버전 (예: Model v1, v2 등) |
xccurrentversion | 지금 앱에서 사용하는 현재 모델 버전을 명시하는 메타파일 |
버전이 여러 개 있을 때, xccurrentversion 파일이 없다면 Core Data는 어떤 모델을 써야 할지 몰라 크래시가 날 수 있다
우측 File Inspector 열어서 세팅 가능
참고