# 코어데이터 프로젝트 생성
코어데이터 기반 앱 제작을 위해 프로젝트 생성 시 옵션을 체크하면 자동으로 생성되는 파일 몇 가지가 있다. .xcdatamodeld
, Persistence.swift
파일이 기본적으로 만들어지고 이니셜 뷰로 사용되는 @main
에 메인 컨텍스트가 environment
로 주입된다.
@main
struct swiftUIMemoApp: App {
@StateObject var store = MemoStore()
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
MainListView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(store)
}
}
}
# PersistenceController.swift
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "SwiftUITest")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
위 코드는 프로젝트 생성 시 자동으로 작성되는 코어데이터 코드 스니펫의 일부인데, 이니셜라이저 부분이 중요하다. inMemory
파라미터는 디스크가 아닌 메모리에 변경사항이 저장되는지 여부를 판단하게 되며 메모리에 저장되는 경우 앱 종료와 함께 변경된 컨텍스트가 업데이트 되지 않기 때문에 처리해둔 코드인 것이다.
PersistenceController
구조체를 코어데이터 싱글톤 매니저 클래스로 정의해보면 아래와 같게 된다.
class CoreDataManager: ObservableObject {
static let shared = CoreDataManager()
let container: NSPersistentContainer
var mainContext: NSManagedObjectContext {
return container.viewContext
}
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "swiftUIMemo")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
이니셜라이저와 컨테이너만 그대로 가져오고 mainContext
속성을 추가하여 CRUD
로직을 구현한다. 데이터 변경이 감지되면 컨텍스트의 hasChanges
속성이 true
로 변경되고 이에 따라 분기처리 하여 코어데이터로의 데이터 업데이트를 진행하면 된다.
import Foundation
import CoreData
@objc(MemoEntity)
public class MemoEntity: NSManagedObject {
}
우선 위와 같이 코어데이터 데이터모델을 기준으로 엔티티를 생성한다.
func saveContext() {
if mainContext.hasChanges {
do {
try mainContext.save()
} catch {
print(error)
}
}
}
func addMemo(content: String) {
let newMemo = MemoEntity(context: mainContext)
newMemo.content = content
newMemo.insertDate = Date.now
saveContext()
}
위와 같이 새로운 메모 엔티티 추가 후 변경된 컨텍스트를 저장하는 식으로 코어데이터에 접근하면 된다. 커스텀 싱글톤 클래스를 정의했으므로 코어데이터 컨텍스트를 엔트리 포인트 뷰에서 environment
모디파이어에 전달하여 하위 뷰에서 접근 가능하도록 하면 셋업은 마무리된다.
@main
struct swiftUIMemoApp: App {
@StateObject var store = MemoStore()
let manager = CoreDataManager.shared
var body: some Scene {
WindowGroup {
MainListView()
.environment(\.managedObjectContext, manager.mainContext)
.environmentObject(store)
}
}
}
# @FetchRequest
@FetchRequest
프로퍼티 래퍼를 사용하면 코어데이터로부터 데이터 요청이 더 간단해진다. environment
모디파이어를 통해 상위 뷰로부터 코어데이터 컨텍스트를 전달받고 있다는 가정 하에 하위 뷰에서 사용 가능한 프로퍼티 래퍼이다. @FetchRequest
는 뷰 속성에 대해 코어데이터로의 데이터 요청 및 생성 요청을 자동적으로 해주도록 한다.
코어데이터 fetch 요청은 쿼리하고 싶은 엔티티에 대한 정보와 요청한 데이터의 정렬 기준인 sort descriptor
두 가지 정보를 필요로 한다.
@FetchRequest(sortDescriptors: [SortDescriptor(\MemoEntity.date, order: .reverse)])
var memoList: FetchedResults<MemoEntity>
키패스 문법을 통해 쿼리할 엔티티 클래스 속성을 참조하는 형태이다. 배열 데이터이므로 요청 완료된 데이터를 리스트에 바인딩해주면 뷰 구성이 쉽게 된다.
List(memoList) { memo in
Text(memo.content)
}
NSPredicate
sortDescriptor
가 데이터 정렬 기준이라면 NSPredicate
은 특정 데이터를 뽑아올 때 사용한다.
NSPredicate(format: "name == %@", "Python")
@FetchRequest
프로퍼티 래퍼 이니셜라이저에서 predicate
파라미터에 위 객체를 전달한다.
@FetchRequest(
sortDescriptors: [SortDescriptor(\.name)],
predicate: NSPredicate(format: "name == %@", "Python")
) var languages: FetchedResults<ProgrammingLanguage>
주의사항
@FetchRequest
프로퍼티 래퍼는 반드시 뷰 안에서만 사용해야 한다.