-
Core Data 파헤치기 🔍iOS 2023. 12. 7. 19:15
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 iOS의 Core Data에 대해 한번 파해쳐보려고 합니다 🙋🏻
왜 파헤치기로 해봤는지?
아주 오래전에..? 거의 처음 iOS를 배울때 Core Data를 학습하면서 해당 블로그 포스팅으로 기재한적이 있더라구요 😮
그래서 참고차 보려했는데, 큰 틀의 기본적인 개념들 위주로 되어 있어서 실제 블로그만 보고 적용이 어려웠어요.
위 블로그는 코어 데이터와 관계형 DB의 개념 설명에 초점을 맞춰서 이러한 녀석이다~ 소개였습니다.
그렇기에 이번 포스팅에서는 코어 데이터를 중점으로 조금 더 면밀히 살펴보면서 실제 코드로 구현까지 해보겠습니다 😃
그럼 시작해볼까요!? 🚀
Core Data
우선 코어 데이터는 iOS 앱에서 데이터를 관리하고 영구적으로 저장하는데 사용되는 프레임워크입니다.
즉, 데이터 모델을 정의하고 이를 기반으로 데이터를 읽고 쓸 수 있죠.
흔히 로컬 즉, 내부 저장을 하는 용도로 별도 외부 라이브러리를 사용하지 않는다면 이 코어데이터를 많이 사용하게 됩니다.
이 외 DB가 무엇인지 코어 데이터의 장단점과 역할 같은 개념에 대해 궁금하신분은 위 CoreData 포스팅을 먼저 보고 이론을 파악하고 오셔도 좋습니다!
해당 포스팅에서는 코어 데이터 사용을 중점적으로 다룹니다ㅎㅎ
Core Data Stack 알아보기
코어 데이터를 공부하면 가장 많이 볼 수 있는 친숙한 그림일거에요.
우선 코어 데이터 스택은 데이터를 관리하고 저장하기 위한 코어 데이터 프레임워크의 핵심 구성요소들의 집합입니다.
좌측부터 살펴볼까요?
Model 부분인 NSManagedObjectModel 객체는 애플리케이션의 데이터 모델을 정의하는 객체입니다.
엔티티와 그들의 속성을 정의하고 이러한 모델은 DB 스키마에 해당됩니다.
Context 부분인 NSManagedObjectContext는 애플리케이션의 데이터를 가져오거나 수정하는데 사용되는 객체입니다.
주로 메모리에서 데이터를 관리하고 DB와 상호 작용합니다.
Store coordinator 부분인 NSPersistentStoreCoordinator 객체는 영구 저장소를 관리하고 DB와 통신을 담당하는 객체입니다.
SQLite, XML, Binary 등 다양한 데이터 저장소 유형과의 연결을 관리해줍니다.
Persistent Store인 NSPersistentStore는 실제로 데이터를 영구적으로 저장하는데 사용되는 데이터베이스 파일이나 다른 형태의 저장소로 볼 수 있습니다.
이렇게 Core Data Stack이 구성되어 있는걸 보았으니 이제 Core Data를 사용해볼께요!
Core Data 사용을 위한 환경 설정하기
두가지 방법으로 Core Data 사용을 위한 환경을 설정할 수 있는데요.
하나는 프로젝트 생성시 Storage에서 None이 아닌 Core Data를 선택해주면 됩니다.
그럼 자동으로 생성이되죠!
다른 방법으로 만약 프로젝트 초기 생성 시 None으로 설정되었다면 파일을 추가하여 만들 수 있어요.
쉽게 Command + N 단축키를 사용하거나 File 메뉴에서 New -> File... 항목을 선택해 새 파일을 만들 수 있습니다.
여기서 코어 데이터 섹션의 데이터 모델을 선택하여 만들어줍니다.
직접 만들었다면 AppDelegate에 코드를 추가해줘야 합니다.
lazy var persistentContainer: NSPersistentContainer = { /* The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. */ let container = NSPersistentContainer(name: "test") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // 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. /* Typical reasons for an error here include: * The parent directory does not exist, cannot be created, or disallows writing. * The persistent store is not accessible, due to permissions or data protection when the device is locked. * The device is out of space. * The store could not be migrated to the current model version. Check the error message to determine what the actual problem was. */ fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }() // 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)") } } }
해당 코드를 AppDelegate 파일에 추가해줍니다.
만약 초기 프로젝트 생성 시 만들어주었다면 해당 코드가 이미 자동으로 추가되어 있을거에요!
여기서도 만약 코어 데이터 파일명이 변경되면 NSPersistentContainer 인스턴스 생성 시 name을 그에 맞게 변경해주면 됩니다.
이렇게 Core Data 사용을 위한 환경 설정이 끝났다면 데이터 모델 파일을 가지고 데이터 모델링을 해줘야 합니다.
Data Modeling
가장 먼저 데이터 모델 파일의 구성 요소부터 하나씩 개념을 살펴보며 추가하는 방법에 대해 알아볼께요.
Entity
데이터베이스의 테이블과 비슷한 개념으로 데이터 모델 내에서 객체의 유형을 정의하고 해당 객체들이 갖는 속성을 나타냅니다.
먼저 편집기 좌측 하단 Add Entity button을 클릭하여 엔티티를 생성할 수 있습니다.
생성을 하면 좌측 상단 Entities list에 생성한 엔티티가 표시됩니다.
만약 엔티티 명을 변경하고 싶으면 해당 엔티티를 더블 클릭하여 한번에 이름을 변경할 수 있습니다.
또한, 우측 Data Model inspector 영역에서도 변경이 가능합니다.
다만, 인스펙터 영역에서 변경한다면 엔티티와 클래스 이름 모두 같이 업데이트 해야 합니다.
자동으로 동기화되어 변경되진 않습니다.
Entity의 주요 속성들
크게 5가지로 볼 수 있습니다.
이 속성들은 Data Model inspector에서 다뤄줄 수 있습니다.
Entity Name
엔티티의 이름으로 해당 필드는 Entities list에 표시된 이름과 동일해요!
Abstract Entity
해당 엔티티를 추상 엔티티로 만들 경우 해당 옵션을 선택합니다.
디폴트로는 체크되어 있지 않고 DB에 실제로 저장되는 엔티티의 형태는 concrete Entity로 생성됩니다.
Parent Entity
유사한 엔티티가 여러 개 있는 경우 Parent Entity를 정의하여 공통 속성을 정의하고 Child Entity가 해당 속성을 상속하도록 할 수 있습니다.
기본적으로 해당 필드는 비어있어요.
Class Name
엔티티의 인스턴스를 만들 때 사용할 클래스 네임입니다.
기본적으로는 엔티티의 이름을 미러링합니다.
클래스 네임을 변경할 경우에는 엔티티의 이름이 변경되지 않기에 확인해줘야 합니다!
Module
엔티티 클래스가 있는 모듈입니다.
기본적으로 코어 데이터는 전역 네임스페이스에서 클래스 파일을 찾습니다.
이렇게 엔티티에 대해 알아봤으니 다음 구성 요소인 Attribute에 대해 알아보겠습니다.
Attribute
데이터베이스의 컬럼 혹은 어트리뷰트와 비슷한 개념으로 엔티티가 가지는 속성들을 나타냅니다.
추가하는 방법으로는 엔티티가 선택된 상태에서 편집기 우측 하단 Add Attribute button을 클릭하여 어트리뷰트를 새로 추가합니다.
그럼 Attributes list에 새로 추가된 어트리뷰트가 나타납니다.
해당 어트리뷰트를 더블 클릭하여 이름을 변경할 수 있습니다.
또 이렇게 해당 데이터의 타입을 지정할 수 있습니다.
기본적으로 제공되는 타입이 아닌 커스텀한 타입을 사용하고자 한다면 Transformable 타입을 선택하고 우측 인스펙터 영역에서 Custom Class를 지정하여 리스트에 노출되는 기본적인 타입 외 다른 커스텀한 타입을 사용할 수 있습니다.
더 디테일한 설정을 위해서는 우측 Data Model inspector를 사용하여 조정할 수 있습니다.
Attribute의 주요 속성들
크게 4가지로 볼 수 있습니다.
Attribute Type
어트리뷰트의 데이터 형식입니다.
해당 필드는 어트리뷰트 리스트에서 선택한 타입과 동일합니다.
Optional
해당 어트리뷰트의 필수 속성을 선택할 수 있습니다.
Validation
해당 어트리뷰트의 유효성 검사 옵션을 지정할 수 있습니다.
Default Value
데이터가 생성되는 시점에 해당 어트리뷰트의 기본값을 지정할 수 있습니다.
이제 마지막으로 Relationship 요소에 대해 알아보겠습니다.
Relationship
두 개 이상의 엔티티를 정의한 후 엔티티간의 관계를 추가할 수 있습니다.
추가하기 위해서는 해당 엔티티를 선택한 상태에서 Relationship 섹션의 하단 + 버튼을 눌러 생성할 수 있습니다.
그럼 Relationships list에 해당 생성한 Relationship을 볼 수 있습니다.
더블 클릭하여 이름을 변경할 수 있고 Attribute 요소와 마찬가지로 여러 속성들을 부여하려면 클릭된 상태에서 우측 인스펙터 영역에서 조정하여 다뤄줄 수 있습니다.
Relationship의 주요 속성들
크게 4가지 속성이 있습니다.
Optional
해당 Relationship의 필수 속성을 선택할 수 있습니다.
Destination
관계를 설정할 대상이 되는 Entity를 선택합니다.
Delete Rule
코어 데이터가 원본 인스턴스를 삭제할 때 변경 내용이 Relationship간에 전파되는 방식을 지정합니다.
Type
Relationship을 1:1 혹은 1:N으로 지정할지 선택합니다.
이렇게 Data Model을 생성하고 모델링을 해봤습니다.
그럼 이제 코드에서 실제 Data를 사용해볼까요?
Core Data 사용하기
예시로 사용하려는 코어 데이터 모델의 엔티티 스펙은 이렇게 사용하려고 합니다.
Car 엔티티에서는 id와 name의 어트리뷰트를 가지는 간단한 구조입니다.
먼저 MVC 기준으로 본다면 사용하고자하는 뷰컨트롤러 파일 코드에서 AppDelegate에 정의된 persistentContainer에 접근해야 합니다.
그렇기에 아래와 같이 사용할 수 있도록 코드를 구현해줍니다.
import UIKit import CoreData class ViewController: UIViewController { var persistentContainer: NSPersistentContainer? { (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } }
아! CoreData 임포트도 필수입니다~
그리고 이제 데이터에 대해 생성 / 조회 / 수정 / 삭제 기능을 구현해볼거에요.
데이터 생성 (Create)
새로 데이터를 생성하기 위한 코드입니다.
func createData() { guard let context = self.persistentContainer?.viewContext else { return } let newCar = Car(context: context) newCar.id = UUID() newCar.name = "benz" try? context.save() }
persistentContainer의 viewContext를 가져와 새로 만들 Car 인스턴스 생성을 위한 Car 타입을 생성 시 해당 context를 넣어줍니다.
그리고 Car 인스턴스에 id, name을 구성하고 context의 save 메서드를 이용해 생성해줍니다.
이러면 코어 데이터 DB에 해당 데이터가 저장되는것이죠.
데이터 조회 (Read)
Car Entity 타입의 Data를 조회하는 코드입니다.
func getData() { guard let context = self.persistentContainer?.viewContext else { return } let request = Car.fetchRequest() let cars = try? context.fetch(request) print(cars) }
생성과 동일하게 persistentContainer의 viewContext를 가져옵니다.
Car 모델의 fetchRequest 메서드를 이용하여 Car 엔티티에 대해 특정 조건에 맞는 데이터를 요청합니다.
그리고 fetch 메서드를 통해 요청한 데이터를 가져옵니다.
현재 코드에서는 Car 엔티티 타입의 모든 데이터를 조회하게 되는것이죠!
데이터 수정 (Update)
기존 데이터를 수정하는 코드입니다.
func updateData() { guard let context = self.persistentContainer?.viewContext else { return } let request = Car.fetchRequest() guard let cars = try? context.fetch(request) else { return } let filteredCars = cars.filter({ $0.name == "benz" }) for car in filteredCars { car.name = "tesla" } try? context.save() }
마찬가지로 fetchRequest와 fetch를 통해 Car 엔티티의 모든 데이터를 가져옵니다.
그리고 filter를 이용해 원하는 조건의 데이터만 분류하여 해당 데이터의 name을 변경해줍니다.
현재 benz name을 가진 데이터들을 tesla로 변경했습니다.
데이터 삭제 (Delete)
기존 데이터를 삭제하는 코드입니다.
func deleteData() { guard let context = self.persistentContainer?.viewContext else { return } let request = Car.fetchRequest() guard let cars = try? context.fetch(request) else { return } let filteredCars = cars.filter({ $0.name == "tesla" }) for car in filteredCars { context.delete(car) } try? context.save() }
update와 비슷합니다.
fetchRequest와 fetch를 통해 Car 엔티티를 가져오고 filter를 통해 삭제하고자하는 데이터를 분류합니다.
그리고 반복문을 통해 context의 delete 메서드를 이용해 해당 tesla name을 가진 모든 데이터를 삭제할 수 있습니다.
이제 위 생성부터 삭제까지 만들어둔 코드를 IBAction에 연결해도 되고 원하는 로직에 자유롭게 심어 구현할 수 있습니다!
마무리
이렇게 이번에 코어 데이터에 대해 어느정도 부셔보면서 사용까지 해봤습니다!
저는 주로 Realm 외부 라이브러리를 사용하여 직접 많이 접해보진 않았지만 그냥 해봤습니다ㅎㅎ
재밌어요 👍
레퍼런스
'iOS' 카테고리의 다른 글
UITest에서 accessibilityIdentifier 활용하기 (78) 2023.12.18 앱 지원 언어 추가 및 기본 언어 변경 (feat. Tuist) (68) 2023.12.14 자동 문자 결합 방지하기 (41) 2023.11.30 CI & DI에 대해 알아보기 🔑 (45) 2023.11.20 ATS (App Transport Security) 톺아보기 (33) 2023.11.14