프로젝트 생성 - coredata 선택
Entity 생성 - > 저장될 데이터들의 집합.
Entity이름 PhoneBook
Attribute(속성)에는 name과 phoneNumber를 추가한다.
→ 전화번호부라는 데이터 집합내에 내부 속성값으로 이름과 휴대폰 번호가 들어간다.
Codegen
개념Code Generator
의 줄임말. Entity 를 어떤 형식의 코드로 생성할 것인지 선택하는 속성.Manual/None
= Entity 의 서브 클래스를 자동으로 생성하지 않고 개발자가 클래스 작성.Class Definition
= Entity 의 서브 클래스를 자동으로 생성.Category/Extension
= Entity 클래스와 함께 extension 을 위한 파일까지 생성.Manual/None
으로 생성해보겠습니다.Create NSManagedObject Subclass 를 생성한다.
그러면 두개의 파일이 생김
이 클래스는 Entity와의 상호작용을 관리한다. (속성값의 저장 및 검색을 처리함)
import Foundation
import CoreData
extension PhoneBook {
//해당 메서드는 swfit에서만 동작하는 메서드임을 명시해놓은것
@nonobjc public class func fetchRequest() -> NSFetchRequest<PhoneBook> {
//fetchRequest PhoneBook에 대한 여러가지 데이터 검색을 돕는다.
return NSFetchRequest<PhoneBook>(entityName: "PhoneBook")
}
//CoreData에 의해 관리되는 객체를 말한다.
@NSManaged public var name: NSObject?
@NSManaged public var phoneNumber: NSObject?
}
extension PhoneBook : Identifiable { //PhoneBook 타입이 고유하게 식별될수 있음을 의미한다.
}
NSPersistentContainer
를 생성한다. (영구 컨테이너)해당 객체는 CoreData에서 데이터를 저장하고 관리하는데 필요한 핵심 객체이다.
이 객체는 CoreData를 사용한다고 체크해놨으니 AppDelegate.swift에 기본적으로 NSPersistentContainer 를 셋팅하는 코드가 존재한다
MARK: - Core Data stack //이부분
하단부 - 문맥을 저장한다.
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges { //데이터가 변경됐다면
do {
try context.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)")
}
}
}
CRUD가 이루어졌으면 해당 메서드를 호출한다.
ViewController에서 데이터에 접근한다. 바로 NSPersistentConatiner에
func createData(name: String, phoneNumber: String){
guard let entity = NSEntityDescription.entity(forEntityName: "PhoneBook", in: self.container.viewContext) else {return} //entity값 설정
let newPhoneBook = NSManagedObject(entity: entity, insertInto: self.container.viewContext) //실제 사용할 객체 ,NSManagedObject는 데이터를 다룰수있음
newPhoneBook.setValue(name, forKey: "name")
newPhoneBook.setValue(phoneNumber, forKey: "phoneNumber")
do {
try self.container.viewContext.save() //변경사항의 저장
print("문맥 저장 성공")
}catch{
print("문맥 저장 실패")
}
}
흐름은 이렇다.
enitity 값을 명확하게 지정해준다. (Core Data의 데이터 구조를 명확히 정의)
그리고 실제 사용할 객체에 해당 entity를 전달하여 연결 시켜준다.
func readAllData() {
do {
let phoneBooks = try self.container.viewContext.fetch(PhoneBook.fetchRequest())
//PhoneBook의 데이터를 가져와 저장
for phoneBook in phoneBooks as [NSManagedObject] {
if let name = phoneBook.value(forKey: "name") as? String,//Any타입에서 String으로 타입캐스팅
let phoneNumber = phoneBook.value(forKey: "phoneNumber") as? String {
print("이름: \(name), 전화번호: \(phoneNumber)")
}
}
}catch{
print("데이터 읽기 실패")
}
//persistentContainer를 현재 뷰컨트롤러에서 사용할수 있게 하는 작업
let appDelgate = UIApplication.shared.delegate as! AppDelegate
self.container = appDelgate.persistentContainer
주의할점 : 델리게이트 패던과는 무관하다.
UIApplication.shared.delegate는 앱의 공용 앱 델리게이트 객체를 참조한다.
import Foundation
import CoreData
@objc(PhoneBook)
public class PhoneBook: NSManagedObject {
public static let className = "PhoneBook"
public enum Key {
static let name = "name"
static let phoneNumber = "phoneNumber"
}
}
클래스 이름과 Key를 전역 변수로 설정 함으로서 클래스 타입에 바로 접근이 가능해진다.
이렇게 되면 만약 속성명을 변경할때에 하나하나씩 변경할 필요없이
PhoneBook클래스 내부에서 딱 한번만 변경해주면 된다.
//MARK: - U: UPDATE
func updateData(currentName: String, updateName: String) {
let fetchRequest = PhoneBook.fetchRequest() //데이터를 가져오고
fetchRequest.predicate = NSPredicate(format: "name == %@", currentName) //조건의 설정
//Predicate는 데이터를 필터링 하기위한 조건을 정의한다. format문자열을 기반으로 필터조건을 설정한다.
//name속성의 값이 currentName변수와 동일해야한다.
//즉, 정리하면 입력된 이름이 위 조건에 맞는 경우에 데이터를 변경하겠다.
do {
let result = try self.container.viewContext.fetch(fetchRequest) //데이터를 가져온다.
//결과 처리
for data in result as [NSManagedObject] {
//데이터 수정
data.setValue(updateName, forKey: PhoneBook.Key.name)
//변경사항저장
try self.container.viewContext.save()
print("데이터 수정 완료")
}
}catch{
print("데이터 수정 실패")
}
}
기존에 존재하는 데이터와 일치한다면 데이터를 변경시킨다.
ex) 이진욱 → 삼진육 으로 교체를 원한다.
이름 : 이진욱 이 존재하면 삼진육으로 교체
fetchRequest.predicate = NSPredicate(format: "name == %@", currentName)
데이터를 가져오되, name == “교체를원하는데이터” 인경우에만 가져온다.
func deleteData(name: String) {
let fetchRequest = PhoneBook.fetchRequest()
fetchRequest.predicate = NSPredicate(format:"name == %@", name)
do{
let result = try self.container.viewContext.fetch(fetchRequest)
for data in result as [NSManagedObject] {
self.container.viewContext.delete(data)
print("삭제된 데이터: \(data)")
}
//변경사항 저장
try self.container.viewContext.save()
print("데이터 삭제완료")
}catch{
print("데이터 삭제 실패: \(error)")
}
}
UserDefault를 사용할때는 따로 프로젝트 생성시 CoreData를 체크하지 않아도 된다.
UserDefault또한 디스크에 데이터를 저장할수 있게 돕는 도구이다.
CoreData보다 사용성이 훨씬 간단하다.
key와 value값을 이용해서 값을 저장한다.
대량데이터 → CoreData (??왤까)
비교적 작은데이터 → UserDefault
코어 데이터를 제대로 활용하려면 비동기 프로그래밍에 대해서 알아야 한다.
//MARK: - Create
UserDefaults.standard.set("010-0000-0000", forKey: "phoneNumber")
Userdefault의 경우 여러공간에 저장가능하지만 가장 일반적인 공간에 저장한다의 의미인 standard를 사용하는것이다.
//여러 타입이 존재하지만 우리는 value를 string으로 선언했으니
//string타입으로 읽으라는것이다.
let phoneNumber = UserDefaults.standard.string(forKey: "phoneNumber")
print("저장된 전화번호: \(phoneNumber!)")
//해당 값은 언래핑을 하지 않으면 Optonal("")이 출력된다.
//값이 존재하지 않을 가능성이 있기 때문에
//MARK: - Update
UserDefaults.standard.set("010-5555-0055", forKey: "phoneNumber")
let newPhoneNumber = UserDefaults.standard.string(forKey: "phoneNumber")
print("바뀐전화번호: \(newPhoneNumber!)")
//MARK: - Delete
UserDefaults.standard.removeObject(forKey: "phoneNumber")
print("전화번호가 남아있나요: \(UserDefaults.standard.string(forKey: "phoneNumber"))")
}
Create와 Update에서 사용했던 set메서드는 원시타입 (정수,문자,실수..)만 가능하다.
만약 클래스나 구조체의 경우 Json을 사용해 인코딩하여 넣을수 있다.