Persist or cache data on a single device, or sync data to multiple devices with CloudKit.
단일 기기의 데이터 캐싱, 유지 또는 CloudKit을 이용한 여러기기의 데이터 동기화
not Database, 일종의 framework
Core Data는 데이터베이스의 직접적인 관리 없이 Swift or Objective-C를 사용해 저장하려고 하는 오브젝트들의 맵핑 디테일을 추상화할 수 있다.

Core Data의 undo manager는 변화를 추적해 그 변화를 각각, 그룹별로 또는 전체를 한번에 되돌릴 수 있다.

흔들어서 되돌리기 기능 뜻하는듯 합니다.
JSON 파싱과 같은 UI를 막을 수 있는 데이터 작업들을 백그라운드에서 진행할 수 있다.
그런 다음 캐싱 또는 저장을 하여 서버의 반복호출을 줄일 수 있다.
Core Data also helps keep your views and data synchronized by providing data sources for table and collection views.
Core Data includes mechanisms for versioning your data model and migrating user data as your app evolves.
사용 하려는 모든 데이터는 우선 메모리에 로딩되는 과정이 존재한다.
Core Data에서 읽고 쓰는 모든 데이터는 원칙적으로 메모리로 로드된 다음에 처리된다.
⇒ (영구 저장소를 아예 사용하지 않고 순수하게 인메모리 방식으로만 사용하는 것이 가능)
일반적인 다른 DB는 테이블을 통해 정의한다.
이들이 일종의 스키마(schema) 역할을 한다

예를들어 사원 정보의 레코드 정보 2개를 읽을 땐 2개의 사원 객체가 생성된다.
데이터 모델을 만든 후 앱의 모델 레이어를 서로 보조해주는 클래스들을 설정하면 된다.
이 클래스들을 Core Data Stack 이라고 부른다.

앱이 시작되는 동안에 Core Data는 초기화를 진행한다.
persistant container를 delegate 안에 처음 사용되기 전까지 인스턴스화를 미루어야 하기 때문에 지연 저장 프로퍼티로 생성한다 ( lazy )
UIKit은 사용법이 인터넷에 잘 나와있으므로 그거 보고 따라하면 된다.
SwiftUI는 비교적 적어 그 내용에 대해 적어보겠다.
특히 SwiftData라고 SwiftUI 친화적인 데이터모델이 있기 때문에 그것을 사용하는게 더 편리해 보이긴 한다.
하지만 SwiftData 또한 CoreData 기반이기 때문에 이거부터 공부하는게 맞겠지?

Data Model 파일을 생성한다.

왼쪽 하단의 Add Entity 를 눌러 엔티티를 생성하고 Attributes를 넣어주면 된다.
Entity가 일종의 스키마라고 생각하면 된다고 한다.
그리고 엔티티 이름을 잘 설정해놔야한다. 저 이름으로 쓰고 불러오기 때문이다.
이 외에도 여러가지 설정이 가능하다.

Class 부분에 Codegen이 있는 것을 볼 수 있다.
Code Generation의 약자로 옵션마다 설정이 약간씩 바뀌며 현재 Class Definition은 얘가 자동으로 데이터모델 클래스를 자동생성 해준다.
커스텀을 하고 싶은 경우에는 저 옵션을 입맛에 맞게 바꿔주면 된다.
위에서 봤듯이 NSPersistentContainer 하나로 손쉽게 사용을 할 수 있기 때문에 이것을 만들면 된다.
import Foundation
import CoreData
struct PersistentController {
static let shared = PersistentController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Model")
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unsolved Error \(error), \(error.userInfo)")
}
}
}
func saveContext() {
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
싱글톤을 사용하여 단 하나의 객체에 접근하게 만들었다.
컨테이너에게 PersistentStores 를 로드 시킨다. ( 저장소 셋업 느낌이라고 한다)
saveContext는 데이터를 디스크에 저장하는 과정이다.
기본적으로 앱 시작 중에 Core Data를 초기화 하기 때문에 기존 UIKit 에서는 AppDelegate 에서 만들며 인스턴스화를 늦추기 위해 lazy 하게 만든다.
하지만 SwiftUI는 AppDelegate가 없기 때문에 static을 사용해 lazy 하게 만든다.
(static 변수 → 전역 변수, 전역 변수 → lazy / ⇒ static = lazy)
앱의 엔트리포인트로 가서 연결시켜주면 된다. (프로젝트명)App.swift ← 이거
import SwiftUI
@main
struct CoreDataStudyApp: App {
let persistentController = PersistentController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistentController.container.viewContext)
}
}
}
shared를 불러와서 container의 context 를 environment로 설정한다.
// in ContentView
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(
entity: Movie.entity(),
sortDescriptors: [.init(keyPath: \Movie.title, ascending: true)],
animation: .spring)
var movies: FetchedResults<Movie>
@Environment 프로퍼티 래퍼를 통해 저장소에 접근한다.
SwiftUI에서 CoreData 사용을 위한 전용 fetch 프로퍼티 래퍼라고 한다. ㄷㄷ
다양한 옵션을 사용할 수 있다.
특히 @FetchRequest 로 데이터를 불러오면 데이터에 변화가 생길때 마다 fetch 해오기 때문에 UI와 연관되어 있을시에 사용하기 좋다.
private func addMovie(title: String, genre: String, releaseDate: Date) {
let movie = Movie(context: managedObjectContext)
movie.title = title
movie.genre = genre
movie.releaseDate = releaseDate
saveContext()
}
private func deleteMovie(at offsets: IndexSet) {
offsets.forEach { index in
let movie = movies[index]
managedObjectContext.delete(movie)
saveContext()
}
}
생성, 삭제 후에 Context를 저장해주어야지 적용된다.
난 ForEach를 사용해 간단하게 만들어봤다.
import SwiftUI
struct ContentView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(
entity: Movie.entity(),
sortDescriptors: [.init(keyPath: \Movie.title, ascending: true)],
animation: .spring)
var movies: FetchedResults<Movie>
@State var isAddMovieViewPresented: Bool = false
var body: some View {
NavigationStack {
VStack {
if movies.isEmpty {
Text("Empty!")
} else {
List {
ForEach(movies) { movie in
NavigationLink(movie.title ?? "nil") {
MovieDetailView(movie: movie)
}
}
.onDelete(perform: deleteMovie)
}
}
}
.navigationTitle("Movie List")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(action: addButton) {
Image(systemName: "plus")
}
}
ToolbarItem(placement: .topBarLeading) {
EditButton()
}
}
.sheet(isPresented: $isAddMovieViewPresented) {
AddMovieView(isPresented: $isAddMovieViewPresented)
.environment(\.managedObjectContext, managedObjectContext)
}
}
}
func addButton() {
isAddMovieViewPresented.toggle()
}
}
데이터베이스 말고도 다른 많은 기능들이 있다고 하는 만큼 시간나면 공부를 해야겠다.
캐시 부분도 궁금하다.
CoreData를 찾아보던 중 SwiftData가 있는 것을 확인했는데, 이건 SwiftUI 친화적이라 해서 이것도 공부해보면 재미가 있을거같다.
아무튼 오늘도 화이팅~
https://github.com/mooninbeom/CoreDataStudy
(전체 코드 참고용)