티스토리 뷰
개요
이전 프로젝트에서는 UserDefaluts를 사용하여 데이터를 저장 / 불러오기를 했었는데, 정석적인 방법은 아니였습니다. (UserDefaluts 는 간단한 사용자 데이터를 저장하고 불러오는 것에 적합했기 떄문에) 그래서 원래 프로젝트에서 더 적합한 방법이기도 하고 이후에 요긴하게 사용할 수 있을 것 같은 CoreData 의 전체적인 구조를 정리해야겠다고 생각을 해서 글을 쓰게 되었습니다.
CoreData
Persist or cache data on a single device, or sync data to multiple devices with CloudKit.
단일 장치에 데이터를 유지하거나 캐쉬할 수 있고 CloudKit을 사용하여 여러 장치에 데이터를 동기화할 수 있는 Framework
CoreData의 스택은 위의 그림과 같이 구성됩니다. NSPersistentContainer 라는 큰 틀에 NSPersistentContainer , NSManagedObjectContext, NSPersistenceStoreCoordinator 와 같은 클래스들이 구성되어 데이터를 관리하는 형태입니다.
실제 예제를 통해서 어떤식으로 동작하는지 한번 봅시다!
사용
프로젝트를 만들 떄 User Core Data를 체크해줍시다.
프로젝트를 생성하면 이렇게 콘텐트 뷰와 함께 Persistence, CoreData_Scene이 생성됩니다. 이들은 Xcode에서 제공하는 SwiftUI 에서의 CoreData 사용 예제 코드들이 포함된 파일입니다.
하나씩 살펴보죠!
CoreData_Scene
먼저 CoreData_Scene 를 봅시다. 이 파일은 .xcdatamodeld 파일 입니다. 아까전의 CoreDataStack에서 NSManagedObjectModel 에 해당합니다. 여기서는 ENTITIES (데이터 개체, 집합) 와 해당 엔터티의 Attribute를 지정하고 관리할 수 있습니다. 일반 데이터 베이스 사용과 매우 유사합니다!
Relationships: 객체간의 관계를 설정할 수 있는 기능을 제공합니다.
Fetched Properties : 해당 객체와 관련된 다른 엔터티의 데이터를 포함하여 Predicate하고 Fetch 할 수 있는 기능입니다. (Filter와 유사한듯)
이 두개의 기능은 아직 잘 모르기도 하고 쓰지 않아서 넘어가겠습니다!
Persistence
import CoreData
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 {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreData_Scene")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
Persistence 는 NSPersistentContainer 를 initialize 하는 PersistenceController 구조체를 담은 파일입니다. 여기서 데이터 저장방식(inMemory)과 url, 사용할 .xcdatamodeld 를 초기화 합니다. (init)
preview 프로퍼티에서는 예제를 위해 데이터를 동적으로 생성하여 저장해주었습니다.
이렇게 생성한 NSPersistentContainer를 사용하기 위해서는 메인 앱에서의 persistenceController 객체 선언이 필요합니다.
import SwiftUI
@main
struct CoreData_SceneApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
CoreData를 사용할 View의 환경변수에 Container의 viewContext를 설정하여줍니다.
이렇게 해서 해당 뷰에서 coreData 사용 준비가 완료 되었습니다!
ContentView
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
ContentView 에서는 이전에 메인 앱에서 생성했던 persistenceController.container 의 viewContext 를 불러오고 FetchReqeust 와 FetchedResults 로 CoreData_Scene에 정의 되어 있는 엔터티인 Item을 정렬하여 가져왔습니다.
모두 눈치 채셨겠지만 NSManagedObjectContext 는 ViewContext를 뜻합니다. 이 ViewContext를 사용해서 데이터를 저장 및 삭제할 수 있습니다.
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
addItem 메소드를 보면 Item 객체를 생성할 때 viewContext를 사용하여 만들고 있습니다. 만들고자 하는 엔터티 객체를 생성할 때 사용하기도 하고 viewContext.save() 로 엔터티가 변경된 사항을 저장하는 역할을 하기도 합니다.
이렇게 해서 원하는 뷰에 CoreData 를 가져와 사용할 수 있습니다!! 와!
NSPersistenceStoreCoordinator
그런데 NSPersistenceStoreCoordinator 는 어디로 갔을까요? 이 친구는 NSManagedObjectContext 가 persistent stores와 communicate 하도록 도와주는 친구입니다. 공식 문서에서는 façade. 즉 인터페이스와 같은 역할을 한다고 묘사 해놓았네요.
NSPersistenceStoreCoordinator 는 Managing Store -(Location, metadata ), Migrating and versioning 등 스토어의 여러 설정들을 해주는 클래스입니다. 아직은 사용해본적 없어서 잘은 모르겠네요!
마치며
프로젝트를 만들면 주어지는 예제를 통해 CoreData Stack 을 정리해보았습니다. NSPersistentContainer , NSManagedObjectContext, NSPersistenceStoreCoordinator 안에 많은 기능들이 있는데, 이제 각각 어떤 역할들을 하는지, 어떤 형태로 쓰는지 알게 되었으니 다음에 실제 사용할 때 조금 수월하게 사용할 수 있지 않을까 싶습니다. 굿!
Reference
https://developer.apple.com/documentation/coredata/core_data_stack + 안에 있는 각각의 NSPersistentContainer , NSManagedObjectContext, NSPersistenceStoreCoordinator 문서 포함
https://sihyungyou.github.io/iOS-coredata/ iOS) 코어 데이터 알아보기
'iOS-Development' 카테고리의 다른 글
Moya 에러 정리 (0) | 2023.09.26 |
---|---|
Lottie (0) | 2022.05.08 |
UserDefaults (0) | 2022.04.19 |
SwiftUI와 Storyboard (0) | 2022.04.03 |
Swift : 타입 (0) | 2022.03.10 |