CoreData
CoreData
종류: Develop
https://github.com/sunny5875/CoreDataStudy.git
CoreData란?
- userDefault보다 좀 더 복잡한 데이터를 저장할 수 있는 프레임워크
기능
- persistence
- 객체를 저장소에 매핑하는 세부정보를 추상화하기 때문에 DB를 관ㄹ리하지 않고도 Swift데이터를 쉽게 저장할 수 있음
- 개별/일괄 변경상황을 undo,redo가능
- 변경사항을 추적하고 개별적/그룹적으로 한번에 롤백할 수 있음
- 백그라운드 데이터 작업
- 백그라운드에서 json을 객체로 분석하는 작업을수행
- 동기화
- DataSource를 제공하기 때문에 동기화 상태로 유지하는데 많은 도움을 줌
특징
- Database 아님!
- cloudKit과 연동이 아주 쉬움
- 내가 가진 기기에서 오브젝트 접근 가능
- sqlite를 쓰긴 하지만 오브젝트 관리를 해준다라고 생각할 것
- SQL도 아님
- Container라는 공간에 데이터를 저장
개념
세개의 레이어 존재
Managed Object model: 클래스, 이 모델을 통해서 오브젝트를 생성
Persistent Store Coordinator: 데이터 저장
Managed object model: 코디네이터와 스토어를 관리
여기서 잠깐 CoreData를 만들려고 보니 SwiftData라는 개념도 나와서 추가 설명 🙂
CoreData VS SwiftData
- 공통점
- 둘다 object relational mapping framework로 data를 영구적으로 관리하는 방법
- 차이점
- SwiftData
- iOS 17부터 사용 가능, CoreData로 마이그레이션 가능
- declarative data modeling
- automatic persistence
- efficient data access: lazy loading하기 때문에 접근이 쉬움
- integration with SwiftUI
- CoreData
- mature framework
- powerful feature
- well-tested
사용 방법
- Data model 파일 추가
- Entity 생성
- 여기서 Entity의 의미
- 저장될 데이터들의 집합
- 여기서 Entity의 의미
- relation 생성
- property를 계산해서 넣지 않아도 relation만 넣어주면 아예 오브젝트끼리 relation이 생성됨
- model의 inspector에서 codegen 선택
- Manual / None(모두 관리할 수 있다는 뜻)
- 관리 객체 하위 클래스의 프로퍼티, 논리를 편집 - Class Definition
- 생성된 논리나 프로퍼티를 편집할 필요가 없을 경우 선택
- 소스 파일 없이 바로 Entity()가 가능해진다는 소리임!! - Category / Extension
- 관리 객체 하위 클래스에 추가적인 메서드나 비즈니스 논리를 추가하고 싶은 경우 선택
- editor에서 createNSManagedObject를 선택
- CoreData Stack을 설정
- persistent container를 생성
- NSManagedObjectModel
- 앱의 타입, 프로퍼티, 관계를 설명하는 앱의 모델 파일
- Entity를 설명하는 database의 스키마
- 앱의 타입, 프로퍼티, 관계를 설명하는 앱의 모델 파일
- NSManagedObjectContext
- 앱 타입의 인스턴스에 대한 변경 사항을 추적
- object를 생성, 저장, 가져오는 작업을 제공
- NSPersistentStoreCoordinator
- 스토어에서 앱 타입의 인스턴스를 저장하고 가져옴
- 영구 저장소와 managedObjectModel을 연결
- NSPersistentContainer
- 모델, 컨텍스트, 스토어 coordinator를 한 번에 설정
- 모든 객체를 포함하는 최상위 칭구
container
- 이곳의 데이터는 모두 공유된 자원을 사용하기에 싱글톤으로 생성
class CoreDataDB {
static let shared = CoreDataDB()
// 여기 이름은 모델 이름을 넣을 것
private let container = NSPersistentContainer(name: "Model")
var context: NSManagedObjectContext {
container.viewContext
}
private init() {
loadStores()
}
private func loadStores() {
container.loadPersistentStores(completionHandler: { _, _ in })
}
func save() {
try? context.save()
}
}
CoreData Stack
- 앱의 모델 레이어를 관리하고 유지하는 역할
저장하는 방법
- NSManagedObjectContext를 가져온다
- Entity를 가져온다
- NSManagedObject를 만든다
- NSManagedObject에 값을 세팅한다
- NSManagedObjectContext를 저장한다.
Optional이나 default값 설정하는 방법
attribute의 inspector를 보면 보인다!!
GUI라고 해서 만만하게 보지 말쟈…
- 디비에 값이 있는데 GUI에 손댔다?? 그러면 migration해줘야한다…;;
- add model version 후에 바꿀 거 바꾸고 기본 모델 설정바꾸고 맵핑 모델 만들면 성공~
- https://yeonduing.tistory.com/48 참고
Unique 제약조건 두는 방법
- inspector에 보면 constaint가 있음 거기에 unique를 두고 싶은 property값을 적으면 됨
Upsert지원하는 방법
- 일단 unique key를 생성하고
- create하는 부분에 mergepolicy 지정하면 됨
func create(_ item: MemoEntity) {
let new = Memo(context: db.context) // context를 가져와서 NSManagedObject를 만든다
new.date = item.date
new.context = item.context
new.title = item.title
new.id = item.id
db.context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump // upsert를 지원하기 위함
db.save() // NSManagedObjectContext를 저장
}
AutoIncrement
- 찾아본 결과로는 지원하지 않는 것으로 보임
- 하지만 내부의 objectId가 있어 대체할 수 있을 것으로 보임
디비 이름 바꿀 때 유의 점
- 단순히 class이름만 rename할 게 아니라 db fetch할 때의 문자열도 바꿔줘야 하고 objc 뒤에 있는 이름도 바꿔줘야 함!
CoreData Tutorial
전체 흐름: 메모를 볼 수 있고 메모를 추가 및 유저를 추가할 수 있습니다.
-
CoreDataModel 파일 생성
-
Entity를 생성
- attribute: 속성을 지정할 수 있습니다.(inspector를 보면 추가적인 다양한 optional, default 등등 선택 가능)
- relationship: 엔티티끼리의 관계를 지정할 수 있습니다.(inspector를 통해 1:1, 1:M 관계 지정 가능)
- fetched property: 약한 단방향 관계, 다른 데이터 객체에서 접근할 수 있는 데이터 객체의 속성을 허용
- Codegen으로 entity class 자동생성
- 속성과 class를 자동으로 생성하지 않고 모두 수정 필요한 경우 manual/none으로 선택
Note:💡 이 때, 모든 엔티티가 다 manual/none이어야 한다!! 하나라도 아니면 에러나니까 조심할 것
class
property
- Container 생성
- 프로젝트 만들었을 때 CoreData를 include했다면 appdelegate에 관련 코드가 이미 있을 것이고 아니라면 아래와 같이 새로 생성하면 됨
- db이기 때문에 싱글톤으로 만드는 경우가 많은 편
import CoreData /// 1. Container 생성 class CoreDataDB { static let shared = CoreDataDB() // 여기 이름은 모델 이름을 넣을 것 private let container = NSPersistentContainer(name: "Model") // context var context: NSManagedObjectContext { return container.viewContext } private init() { loadStores() } // store private func loadStores() { container.loadPersistentStores(completionHandler: { _, _ in }) } func save() { do { try context.save() } catch { let nserror = error as NSError NSLog("Unresolved error \(nserror), \(nserror.userInfo)") } } }
- Repository 및 entity 생성
- 여기서의 entity는 view, viewModel에서 사용하는 entity를 의미
struct UserEntity: SelfReturnable, Hashable {
var name: String
var nickname: String
var isGirl: Bool = false
}
import Foundation
import CoreData
final class UserRepository {
private let db = CoreDataDB.shared
private func getAll() -> [User] {
let request: NSFetchRequest<User> = User.fetchRequest()
let result = try? db.context.fetch(request)
return result ?? []
}
private func getItem<T: Equatable>(_ keyPath: WritableKeyPath<User, T>, _ value: T) -> User? {
getAll().filter { $0[keyPath: keyPath] == value }.first
}
}
extension UserRepository: ItemRepositoriable {
typealias Entity = UserEntity
func create(_ item: UserEntity) {
let new = User(context: db.context) // context를 가져와서 NSManagedObject를 만든다
new.name = item.name
new.nickname = item.nickname
new.isGirl = item.isGirl
db.context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump // upsert를 지원하기 위함
db.save() // NSManagedObjectContext를 저장
}
/*
- Entity 지정 (FROM): 필수 - NSFetchRequest
- 검색 조건 지정 (WHERE): 생략 가능 - NSPredicate
- 정렬 조건 지정 (ORDER BY): 생략 가능 - NSSortDescriptor
*/
func edit(_ item: UserEntity) {
let request: NSFetchRequest<User> = User.fetchRequest()
request.predicate = NSPredicate(format: "name == %@", item.name)
let object = try? db.context.fetch(request).first
object?.name = item.name
object?.nickname = item.nickname
object?.isGirl = item.isGirl
db.save()
}
func getItem<T: Equatable>(_ keyPath: WritableKeyPath<UserEntity, T>, _ value: T) -> UserEntity? {
self.getAllItems().filter { $0[keyPath: keyPath] == value }.first
}
func getAllItems() -> [UserEntity] {
let request: NSFetchRequest<User> = User.fetchRequest()
let result = try? db.context.fetch(request).map { $0.toEntity }
return result ?? []
}
func delete(_ item: UserEntity) {
let object: User? = self.getItem(\.name, item.name)
guard let object else { return }
db.context.delete(object)
db.save()
}
}
** Note **💡 나중에 데이터베이스의 이름을 바꿀 경우, rename뿐만 아니라 @objc(모델이름)과 “모델이름”도 다 바꿔줘야 함
- 만약 모델에 대한 처리가 더 필요해서 마이그레이션을 해야하는 경우
-
Editor → add Model version으로 모델 추가
- 필요한 수정 진행
-
기본 모델을 새로운 모델로 변경(바꾼 경우 해당 모델에 초록색 check가 file 구조에 보임)
-
mapping model 생성
-