CoreData

종류: Develop

https://github.com/sunny5875/CoreDataStudy.git

CoreData란?

  • userDefault보다 좀 더 복잡한 데이터를 저장할 수 있는 프레임워크

기능

  • persistence
    • 객체를 저장소에 매핑하는 세부정보를 추상화하기 때문에 DB를 관ㄹ리하지 않고도 Swift데이터를 쉽게 저장할 수 있음

    Untitled

  • 개별/일괄 변경상황을 undo,redo가능
    • 변경사항을 추적하고 개별적/그룹적으로 한번에 롤백할 수 있음
  • 백그라운드 데이터 작업
    • 백그라운드에서 json을 객체로 분석하는 작업을수행
  • 동기화
    • DataSource를 제공하기 때문에 동기화 상태로 유지하는데 많은 도움을 줌

특징

  • Database 아님!
  • cloudKit과 연동이 아주 쉬움
    • 내가 가진 기기에서 오브젝트 접근 가능
  • sqlite를 쓰긴 하지만 오브젝트 관리를 해준다라고 생각할 것
  • SQL도 아님
  • Container라는 공간에 데이터를 저장

개념

세개의 레이어 존재

스크린샷 2023-10-26 오전 11.12.21.png

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

Untitled

사용 방법

  1. Data model 파일 추가
  2. Entity 생성
    1. 여기서 Entity의 의미
      1. 저장될 데이터들의 집합
  3. relation 생성
    1. property를 계산해서 넣지 않아도 relation만 넣어주면 아예 오브젝트끼리 relation이 생성됨
  4. model의 inspector에서 codegen 선택
    • Manual / None(모두 관리할 수 있다는 뜻)
    • 관리 객체 하위 클래스의 프로퍼티, 논리를 편집 - Class Definition
    • 생성된 논리나 프로퍼티를 편집할 필요가 없을 경우 선택
    • 소스 파일 없이 바로 Entity()가 가능해진다는 소리임!! - Category / Extension
    • 관리 객체 하위 클래스에 추가적인 메서드나 비즈니스 논리를 추가하고 싶은 경우 선택
  5. editor에서 createNSManagedObject를 선택
  6. CoreData Stack을 설정
    1. persistent container를 생성

Untitled

Untitled

  • 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

  • 앱의 모델 레이어를 관리하고 유지하는 역할

저장하는 방법

  1. NSManagedObjectContext를 가져온다
  2. Entity를 가져온다
  3. NSManagedObject를 만든다
  4. NSManagedObject에 값을 세팅한다
  5. NSManagedObjectContext를 저장한다.

Optional이나 default값 설정하는 방법

attribute의 inspector를 보면 보인다!!

스크린샷 2023-10-26 오후 2.25.09.png

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

전체 흐름: 메모를 볼 수 있고 메모를 추가 및 유저를 추가할 수 있습니다.

  1. CoreDataModel 파일 생성

    스크린샷 2023-10-31 오후 1.46.16.png

  2. Entity를 생성

    1. attribute: 속성을 지정할 수 있습니다.(inspector를 보면 추가적인 다양한 optional, default 등등 선택 가능)
    2. relationship: 엔티티끼리의 관계를 지정할 수 있습니다.(inspector를 통해 1:1, 1:M 관계 지정 가능)
    3. fetched property: 약한 단방향 관계, 다른 데이터 객체에서 접근할 수 있는 데이터 객체의 속성을 허용

스크린샷 2023-10-31 오후 1.46.48.png

  1. Codegen으로 entity class 자동생성
    1. 속성과 class를 자동으로 생성하지 않고 모두 수정 필요한 경우 manual/none으로 선택

스크린샷 2023-10-31 오후 1.50.34.png

스크린샷 2023-10-31 오후 1.52.04.png

Note:💡 이 때, 모든 엔티티가 다 manual/none이어야 한다!! 하나라도 아니면 에러나니까 조심할 것

class

class

property

property

  1. Container 생성
    1. 프로젝트 만들었을 때 CoreData를 include했다면 appdelegate에 관련 코드가 이미 있을 것이고 아니라면 아래와 같이 새로 생성하면 됨
    2. 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)")
               }
         }
     }
    
  2. Repository 및 entity 생성
    1. 여기서의 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(모델이름)과 “모델이름”도 다 바꿔줘야 함

  1. 만약 모델에 대한 처리가 더 필요해서 마이그레이션을 해야하는 경우
    1. Editor → add Model version으로 모델 추가

      스크린샷 2023-10-31 오후 3.51.15.png

    2. 필요한 수정 진행
    3. 기본 모델을 새로운 모델로 변경(바꾼 경우 해당 모델에 초록색 check가 file 구조에 보임)

      스크린샷 2023-10-31 오후 3.51.30.png

    4. mapping model 생성

      스크린샷 2023-10-31 오후 3.52.54.png