Closed seoulboy closed 3 years ago
Core Data는 데이터베이스다.
Core Data는 데이터베이스가 아니다.
Core Data는 SQLite다.
???
언제나 그렇듯 처음에 보면 언뜻 이해가 잘 가지 않는, 서론에 적혀있는 추상적인 정의를 읽어보았다.
Core Data is an object graph management and persistence framework in the mac OS and iOS SDKs.
That means Core Data can store and retrieve data, but it isn't a relational database like MySQL or SQLite. Although it can use the SQLite as the data store behind the scenes, you don't think about Core Data in terms of tables and rows and primary keys.
그러니까 Core Data는 데이터를 저장하고 가져올 수 있는데, MySQL 이나 SQLite 같은 데이터베이스는 아니다. SQLite를 data store로 사용할 수 있는데, Core Data는 테이블과 열과 프라이머리 키로 이루어진 구조는 아니다.
... 라는데.
그러니까 Core Data는 데이터를 저장하고 가져올 수 있는 저장공간이면서 table, row, primary key 이러한 개념이 아닌 다른 개념으로 구성되어있고, SQLite로 조작이 가능한 친구라는 것 같다.
계속해서 읽어보자.
먼저 managed object model 이 필요하다. Data Model Editor을 통해 만들 수 있다.
Data Model 파일인 .xcdatamodel은 File > New File > Data Model 검색 -> Create 를 통해 파일을 생성할 수 있다.
👆 Xcode의 Data Model editor. Managed Object Model을 생성하고 수정할 수 있다.
Add Entity 를 통해 생성하고, + 버튼을 통해 attributes를 생성할 수 있다. Attributes는 기본적인 타입 설정이 가능하다. Custom type을 적용하고 싶으면 transformable 이라는 것을 사용해야한다. - transformable 에 대한 얘기는 잠시 미뤄두겠다.
Person 이라는 entity를 생성하고, name 이라는 String 타입의 attribute 를 추가해주었다.
뷰컨에 var people = [String]()
<- 배열의 내용에 따라 cell의 내용이 채워지는 테이블 뷰가 있다고 가정하자. 각각의 배열의 요소에는 사람들의 이름이 담겨있다.
배열에 이름을 추가할 때마다 테이블 뷰에 이름이 표시되고 있다. 그러나 앱을 완전히 종료하고 다시 열면 데이터가 사라져있다.
코어데이터를 사용해서 저장하는 법을 간략하게 알아보자
우선 배열을 다음과 같이 NSManagedObject
변경시켜줘야한다.
var people = [NSManagedObject]()
NSManagedObject
는 CoreData에 저장된 하나의 오브젝트이다. 이것이 있어야만 CRUD 작업을 할 수 있다. NSManagedObject
는 어떤 entity도 될 수 있다.
NSManagedObject에는 value(forKeyPath: )
라는 메서드가 있는데, 이 메서드를 통해 attribute의 값을 가져올 수 있다.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let person = people[indexPath.row]
let cell =
tableView.dequeueReusableCell(withIdentifier: "Cell",
for: indexPath)
cell.textLabel?.text =
person.value(forKeyPath: "name") as? String // Person entity의 "name" attribute에 저장된 값에 접근하는 부분
return cell
}
}
NSManagedObject
는 위에 Data Model에서 입력한 Person entity의 name attribute에 대해 모르기 때문에, Key-value Coding (KVC)을 통해 String 으로 직접 입력해주어야 한다. - 이는 Managed Object 를 SubClass해서 클래스에 속성에 접근하듯이 사용할 수도 있다. - Managed Object Subclass 에 대한 얘기도 잠시 미뤄두겠다.
자 이제 드디어 코어데이터에 저장하는 로직이 담겨있는 부분이다.
아래 함수는 이름을 입력한 후에 매개변수로 입력한 이름이 문자열로 받으면서 호출이된다는 상상을 해보자.
func save(name: String) {
// 1
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
// 2
let managedContext =
appDelegate.persistentContainer.viewContext
// 3
let entity =
NSEntityDescription.entity(forEntityName: "Person",
in: managedContext)!
// 4
let person = NSManagedObject(entity: entity,
insertInto: managedContext)
// 5
person.setValue(name, forKeyPath: "name")
// 6
do {
try managedContext.save()
people.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
NSPersistentContainer
라는 친구가 그곳에 있는데, 그 친구를 통해서 managedContext를 가져올 수 있다. 코어 데이터에서 하는 대부분의 동작, 저장 및 불러오기는 이 context를 통해 이뤄지기 때문에 사용한다. persistentContainer.viewContext
viewContext를 변수에 담는다. 이는 다음 몇줄의 코드에서 여러번 사용된다NSEntityDescription
은 Data Model에 있는 entity와 코드를 연결하는 연결고리이다. entity(forEntityName:, in:)
메서드를 사용해서 생성과 동시에 context에 넣어준다.
NSManagedObject(entity:, insertInto:)
Person 이라는 managed Object를 생성하고 context에 추가해준다. - Entity는 클래스 정의이고 Managed Object는 해당 클래스의 인스턴스라고 생각하면 된다.do catch
문안에서 실행한다.
save()
를 호출한다. 그리고 people 배열을 갱신해준다. 이렇게하면 CoreData에 값을 저장할 수 있다.
저장한 값을 불러오는 방법도 빠르게 살펴보자. 저장한 데이터를 불러오는 방법은 방금 전 확인한 저장 방법과 유사하면서 조금 더 간단하다.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Person")
//3
do {
people = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
viewDidLoad()
안에서 해주어도 좋다do catch
문 안에서 실행한다.
context.fetch()
메서드에 위에서 생성한 fetch request를 담아서 실행한다. - 이렇게 하면 CoreData에 저장 되어있는 Person 들이 [NSManagedObject]
반환된다.NSManagedObject
는 Core Data entity의 런타임 representation이다. Key-Value Coding을 통해 attribute을 불러오거나 수정할 수 있다.NSManagedObjectContext
가 필요하다. (context.save()
/ context.fetch(_:)
)Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Person''
-> NSPersistentContainer 생성자에 들어가는 name의 매개변수와 xcdatamodel 파일명이 동일한지 확인한다.
Thread 1: "[<Person 0x600001d400a0> setValue:forUndefinedKey:]: the entity Person is not key value coding-compliant for the key \"name\"."
-> xcdatamodel 파일의 Person entity 의 attribute 명이 name과 동일한지 확인한다.
func save(name: String) {
// get reference to AppDelegate
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
// get reference to managedContext
let managedContext = appDelegate.persistentContainer.viewContext
// get link to entity and add it to context
let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
// add object to context
let person = NSManagedObject(entity: entity, insertInto: managedContext)
// set the name attribute value
person.setValue(name, forKeyPath: "name")
// commit changes by calling context.save()
do {
try managedContext.save()
people.append(person)
} catch let error as NSError {
print("\(error) \(error.userInfo)")
}
}
-> let person = NSManagedObject(...) 다음 줄에 있는 person.setValue() 메서드를 잊지말고 호출해주어서 value를 업데이트해주자.
의논거리 🤔