기어가더라도 제대로

[iOS - Swift] CoreData 용어 정리 및 CRUD 사용법 본문

Swift - 데이터베이스

[iOS - Swift] CoreData 용어 정리 및 CRUD 사용법

Damagucci-juice 2022. 10. 20. 09:00

목차

- core data 는 무엇?
- DB 와 다른가? ORM은 무엇인가?
- Application's Model Layer
- Data Model 만들기
- Entity 란? 
- Core Data Model로 부터 Swift Class 만들기(3가지 방법)
- Core Data Stack 이란? 그리고 생성하기
- NSManagedObjectModel
- NSManagedObjectContext
- NSPersistentStoreCoordinator
- NSPersistentContainer
- 뷰와 연결하기
- 사례 예시: Business, Department, Employee :: Core Data Model
- Get, Add, Delete, Update

 

Core Data는 무엇인가?

  • 앱의 모델 계층
  • 객체 그래프 관리 프레임워크
  • 메모리에 있는 관계 그대로 디스크에 쓰고 싶을 때 사용한다.

DB와 다른것인가? ORM 은 무엇인가?

  • 엄밀히는 DB는 아니다. Core Data 프레임워크에서 제공하는 기능 중에 하나가 DB 기능이다.
    • DB 저장 + DB 검색
  • ORM 은 DB에 CRUD를 할 때 Swift 언어에서 하는 방식처럼 사용하게 해주는 기능이다.
    • 복잡한 쿼리문을 배울 필요 없이 간편하게 api로 db와 통신 가능

Application의 Model Layer

  • Domain 계층의 Model 객체들을 Device도 이해하는 객체로 변환한 것이라 이해하면 편하다.

Data Model 만들기

  • Command N → Data Model

Entity란?

  • Entities 에 들어가는 하나의 클래스
  • Entity 하나는 편하게 DB에서 Table 하나라고 보면 된다.
  • Attributes, Relationships, Fetched Properties 는 컬럼 역할
    • Attributes: 클래스에 속한 프로퍼티
    • RelationShips: Entity간에 관계를 나타냄

Attributes에서 배열타입 설정

  • repeats 라는 프로퍼티에 [Int] 타입을 설정하고 싶은 경우
  • Attribute 레코드 클릭 -> Type을 "Transformable" 로 변경 -> Inspector -> Attribute - Custom Class -> "바꾸고 싶은 타입으로 설정"

Relationships에서 배열 타입 설정

  • Person Entity가 Family에 여럿 있을 수 있는 경우 members: [Person]
    • Family 기준 → Members 프로퍼티에 Person 배열이 있다. inverse 는 Person 기준에서 family는 어떤 프로퍼티에 있는가 를 정의
    • Person 기준 → family 프로퍼티에 Family가 있다. Family에서 Person은 members 프로퍼티에 속한다.
    • 서로의 역관계도 설정을 해놓아야 변화를 감지할 때 양방향으로 전달

  • Family Entity는 여러개의 Person을 가질 것이니까 Relationships에서 항목을 하나 클릭하고
    • 해당 로우 클릭 → Inspector → Type → “To One”에서 “To Many” 로 변경
    • members에서 여러 Person으로 그래프가 갈 수 있도록 설정

custom type 을 attribute의 type 으로 채택하기

  • 위의 unit 속성을 Unit 이라는 enum 케이스 타입으로 채택할 것임
  • type 과 사용할 enum 의 Int16 타입을 맞추고, Objective-C 에서 사용가능하다는 키워드를 달아줘야함
  • 기본으로 설정되는 Optional 값을 체크 해제하고 기본값을 설정

@objc
public enum Unit: Int16 {

    case hour = 0
    case minute = 1
    case km = 2
    case times = 3

}

  • 위의 Unit 이란 타입을 프로퍼티로 사용할 부모 Entity로 가서
  • Inspecter ->. Class -> Codegen -> Manual/None
  • Navigator Bar -> Editor -> Create NSManagedObject Subclass ... : 로 하면 자동으로 두가지 파일이 생성됨

  • Notice+CoreDataProperties 에서 extension으로  unit 프로퍼티에 대한 타입을 지정해줄 수 있음
extension Notice : Identifiable {
    @NSManaged public var unit: Unit
}
  • 추가 팁: 기존에 시뮬레이터에서 구동중인 데이터가 있고 이를 커스텀 타입으로 변경했을 때 오류가 나는데, 이것을 마이그레이션 하는 것도 재밌는 학습이 될거라 생각합니다. 
  • 미세먼지 팁: 위에서 enum 을 했는데 struct 는 되나 검색을 해보니, 지원하지 않는다고 하네요. 그래서 방법이 Data 타입은 지원해주니, struct 를 Data 로 형변환해서 저장하는 방법이 일단은 떠오릅니다. 

 

Core Data Model로 부터 Swift Class를 만드는 3가지 방법

  • Data Model Inspector → Entity → Class → Codegen
  • 여기서 3가지 방식으로 Swift Class 를 만들어주는 방법이 존재한다.
  • Xcode Editor에서 만들어준 Entity를 어떻게 Swift 에서 Class 처럼 사용할지 설명한다.
  • 각 방식간에 전환 설정은 꼭 클린 빌드 폴더를 해야 전환이 된다.
  • Class Definition
    • 별다르게 하지 않아도 Swift 타입으로 인식
    • 해당 타입 안에서 메서드나 기타 수정 불가
  • Manual/None
    • 수동으로 Entity에 해당하는 Swift Class 파일을 생성
    • 이 모드로 전환 후 Xcode 상단 Bar → Editor → Create NSManagedObject Subclass
    • 자동으로 파일 추가됨
  • Category/Extension
    • Class Definition 처럼 자동으로 추가가 되지만, 추가로 선언하고 메서드나 연산 프로퍼티에 대해서
    • Extension 으로 추가

Core Data Stack 이란? 그 생성법

  • 4가지 객체가 있는데, 가장 중요한 것은 Context이다.
  • ORM답게 Context의 변화를 감지하고 DB에 자동으로 전달하기 때문이다.
    • 개발자가 엄격하게 DB의 CRUD 문법을 전달하지 않아도 된다.
  • Core Data 내부에 SQL 기능을 편리하게 사용할 수 있도록 도와주는 도구의 집합이다.

NSManagedObjectModel

  • 객체의 그래프 관리가 Core Data의 주 업무라고 했는데, 그 그래프를 담당한다.
  • .xcdatamodeld 에 있는 Entity들의 프로그래밍적인 표현이다.
  • 주로 사용하진 않는다.

NSManagedObjectContext

  • managed object에 변화를 추적하고 조작하는 객체 공간
  • 데이터 베이스에 있는 객체를 보고 접근하게 해주는 Window
  • 개발자가 주로 사용
    • 항목의 생성, 수정, 삭제, 검색등의 작업을 한다.
  • Core Data Class에 생긴 변화를 추적한다.

NSPersistentStoreCoordinator

  • Context와 persistent store의 소통을 돕기 위해 모델을 사용하는 coordinator
  • Persistent Store를 관리
  • 실제 데이터베이스를 관리해 store로부터 앱의 타입의 인스턴스들을 저장하고 fetch 한다.
    • Context의 변화를 읽고 실제 store에 저장하거나 검색하는 일을 대신 해주는 기특한 녀석
  • 자주 사용하진 않는다.

NSPersistentContainer

  • 위의 3개, 즉 Core Data Stack을 캡슐화한 컨테이너이다.
  • 이 클래스에 프로퍼티로 위에 3녀석이 자리하고 있다.
  • Core Data Stack의 생성과 관리를 간단하게 해준다.
  • Core Data의 DB라고 할 수 있다.


뷰와 연결하기

  • persistent container 만 만들어주면 core data stack 모두를 사용할 수 있다.
struct PersistenceController {
  static let shared = PersistenceController()

  let container: NSPersistentContainer

  init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "appName")
    container.loadPersistentStores { _, error in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    }
  }
}
  • .xcdatamodeld의 파일 이름과 container의 이름이 같게 컨테이너를 만든다.
  • persistent store load 명령을 내린다.
  • SwiftUI에서는 ~App.swift 파일에서 사용

데이터를 디스크에 저장하는 메소드 만들기

// ~ PersistenceController

func saveContext() {
  let context = container.viewContext
  if context.hasChanges {
    do {
      try context.save()
    } catch {
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
  }
}
  • persistent container에 viewContext를 통해 데이터베이스에 접근
  • context.hasChanges 를 사용해 변화가 있을 때 저장

앱의 나머지 부분에 연결하기

struct CoreDataPracticeApp: App {
  let persistenceController = PersistenceController.shared

  var body: some Scene {
    WindowGroup {
      ContentView()
        .environment(\.managedObjectContext, persistenceController.container.viewContext)
    }
  }
}
  • context를 .managedObejctContext 라는 키값으로 전달한다.
  • SwiftUI 앱의 다른 부분에서도 @Environment 에 접근하여 사용 가능하다.

뷰에서 managed object context 사용하기

// ContentView

@Environment(\.managedObjectContext) var managedObjectContext

func saveContext() {
  do {
    try managedObjectContext.save()
  } catch {
    print("Error saving managed object context: \(error)")
  }
}
  • 변화를 저장하기 위해서 항상 managedObjectContext.save() 를 해야한다.

객체를 fetch 해오기

@FetchRequest(
  entity: Movie.entity(),
  sortDescriptors: [
    NSSortDescriptor(keyPath: \Movie.title, ascending: true)
  ],
  predicate: NSPredicate(format: "genre contains 'Action'")
) var movies: FetchedResults<Movie>
  • @FetchRequest : 데이터에 변경이 생길 때마다 계속 fetch 해오는 속성
  • entity: 가져올 entity
  • sortDescriptors: entity의 인스턴스들을 어떤 기준으로 정렬해 가져올 것인지 설정
    • Movie.title을 기준으로 오름차순으로 가져옴
  • predicate: 일종의 필터링 과정. Movie.genre 에서 “Action” 이 있는 객체만 걸러짐
    • NSPredicate 문법이 복잡하고 다양해서 따로 검색을 해보는 것을 추천

CRUD 메소드 구현

// ContentView

func addMovie(title: String, genre: String, releaseDate: Date) {
  let newMovie = Movie(context: managedObjectContext)

  newMovie.title = title
  newMovie.genre = genre
  newMovie.releaseDate = releaseDate

  saveContext()
}

func deleteMovie(at offsets: IndexSet) {
  offsets.forEach { index in
    let movie = self.movies[index]
    self.managedObjectContext.delete(movie)
  }
  saveContext()
}
  • 생성 및 삭제 후 saveContext() 해줌

 




예시) Business, Department, Employee - Core Data Model sample

[Add, Fetch] Entity - Business 회사 가져오기

class RelationshipViewModel: ObservableObject {
  let manager = CoreDataManager.instance
  @Published var businesses: [BusinessEntity] = []
  @Published var departments: [DepartmentEntity] = []
  @Published var employees: [EmployeeEntity] = []

  init() {
    getBusinesses
  }

  func addBusiness() {
    let newBusiness = BusinessEntity(context: manager.context)
    newBusiness.name = "Apple"

    //비지니스에 부서 추가하기
    //newBusiness.departments = []

    //비지니스에 직원 추가하기
    //newBusiness.employees = []

    //회사에 부서 추가하기 
    //newBusiness.addToDepartments(value: DepartmentEntity)

    //비지니스에 직원 추가하기
    //newBusiness.addToEmployees(value: EmployeeEntity)
    save()
  }

  func getBusinesses() {
    let request = NSFetchRequest<BusinessEntity>(entityName: "BusinessEntity")
    do {
        businesses = try manager.context.fetch(request)      
    } catch let error {
      print("Error fetching. \(error.localizedDescription)")
    }

  }

  func save() {
    manager.save()
  }
}

[Fetch] Entity - 회사에 속한 부서들과 직원들 가져오기

struct BusinessView: View {
  let entity: BusinessEntity

  var body: some View {
    VStack {
      Text("Name: \(entity.name ?? "")")
          .bold()

      if let departments = entity.departments?.allObjects as? [DepartmentEntity] {
        Text("Departments:")
            .bold()
        ForEach(departments) { department in
             Text(department.name ?? "")
        }
      }

      if let employees = entity.employees?.allObjects as? [EmployeeEntity] {
        Text("Employees: ")
            .bold()
        ForEach(employees) { employee in
            Text(employee.name ?? "")
        }
      }
    }
  }
}

[Delete] Entity - 부서 삭제

// ~ RelationshipViewModel
func deleteDepartment() {
  let department = departments[2]
  manager.context.delete(department)
  save()
}

[Update] Entity - 회사의 정보 수정

// ~ RelationshipViewModel
func updateBusiness() {
  let existingBusiness = businesses[2]
  existingBusiness.addDepartments(departments[1])
  save()
}

Entity 자체가 삭제되었을 때 Relationships 보존 전략

https://slid-users-assets-v1-seoul.s3.ap-northeast-2.amazonaws.com/public/image_upload/1b75b30cd8db42f7b0d9b8c76b93d848/ececc73a-b5a1-4907-b234-44d57d06fc67.png

  • Data Model 에서
  • Entity: DepartmentEntity
  • Relationships: employees
  • Delete Rule 설정으로 데이터를 삭제할 때 관계된 것들을 어떻게 남겨둘지 삭제할지 전략을 세울 수 있다.

Nullify

"Finance" 부서가 삭제되면 소속 직원인 "Emily" 는 자신이 속한 부서만 Null로 될 뿐 employeeEntity로 남아있다.

Cascade

"Finance" 부서가 삭제되면 모든 소속 직원 EmployeeEntity 가 삭제된다. 즉, "Emily" 도 삭제된다.

Deny

  • 부서에 속한 모든 employees 가 없어질 때까지 해당 부서의 삭제를 거부함
  • 이 경우 emily가 있기 때문에 "Finance" 부서 자체의 삭제가 거절되고 에러를 반환

출처

https://velog.io/@nala/iOS-Core-Data는-대체-무엇인가

 

[iOS] Core Data는 대체 무엇인가???

객체 그래프 관리 프레임워크라는데?

velog.io

https://velog.io/@nala/iOS-SwiftUI에서-CoreData-써보기#1-3-core-data-model-file로부터-swift-class를-생성한다

 

[iOS] SwiftUI에서 CoreData 써보기

이 글을 쓰는 나와 나를 지켜보는 n년차 개발자

velog.io

https://www.hackingwithswift.com/forums/swiftui/adding-an-array-of-objects-to-an-nsset-relationship/10081

 

SOLVED: Adding an Array of Objects to an NSSet Relationship – SwiftUI – Hacking with Swift forums

Hey All, this is a continuation to my previous post about Many to Many Relationships in Core Data. I wanted to elaborate further and update you all with my current problem, hoping someone might be able to help me get over this current stump. I will attach

www.hackingwithswift.com

https://www.youtube.com/watch?v=huRKU-TAD3g&t=967s&ab_channel=SwiftfulThinking

https://stackoverflow.com/questions/26900302/swift-storing-states-in-coredata-with-enums

 

Swift: Storing states in CoreData with enums

I want to store an enum state for a managed object within CoreData enum ObjStatus: Int16 { case State1 = 0 case State2 = 1 case State3 = 3 } class StateFullManagedObject: NSManagedObj...

stackoverflow.com

 

Comments