우리는 앱이 종료되고 실행되고 상관없이 저장되어 있으면 하는 데이터가 있을 것이다. 물론 서버에 저장해 놓고 매번 호출해서 데이터를 받을 수 있겠지만 앱 자체에서 데이터를 저장해 놓는 방법도 당연히 있지 않을까?
ㅇㅇ 당연히 있다. 이부분이 바로 Persistence(영구성)에 대한 개념이다. 영구 없다 그 영구 말고 ^^
대표적으로 많이 언급되고 접할 수 있는건 UserDefaults와 Core Data이다. 하지만 사용방법과 진입장벽은 좀 차이가 있어서 일단 비교적 쉽게 가벼운 데이터를 저장해 놓을 수 있는 UserDefaults를 알아보고 비교적 진입장벽이 높지만 무거운 데이터를 효율적으로 저장해 놓을 수 있는 Core Data에 대해 알아보겠다.
[iOS] Core Data (1) | Persistence
[iOS] Core Data (3) | codegen(generating code) 이해해보자!
# 🥨 UserDefaults와 Core Data
Core Data가 비교적 진입장벽이 높다고 앞에서 말했는데 그럼 뭐가 다른걸까?
차이 정도는 알아야지!
- 일단 사용 시점이 다르다. UserDefaults는 가벼운 데이터, 단순한 데이터를 저장할 때 주로 사용(UserDefaults는 사용자 기본 설정과 같은 단일 데이터 값에 적합)하고, Core Data는 비교적 무거운 데이터를 저장할 때 사용한다.
- 그리고 어떻게 저장하는가 구조 측면에서 좀 다른데, UserDefaults는 Key-Value의 구조(key의 값은 String)를 가지고 있다면 Core Data 안에는 다양한 Entity가 있을 수 있고 이 Entity 안에 다양한 Attribite들이 있고 각 Attribite에 해당하는 값들이 존재하게 된다.
이렇게만 봐도 UserDefaults와 Core Data가 따로 존재하는 이유를 아시겠죠잉
그리고 UserDefaults사용은 이렇게 요약할 수 있습니다. 일단 이 글은 Core Data에 초점이 맞춰져 있으니 UserDefaults는 가볍게 알아보고 넘어가기!
//데이터 저장
UserDefaults.standard.set(sender.isOn, forKey: "switchState")
//데이터 패치
UserDefaults.standard.bool(forKey: "switchState")
# 🥨 core data
일단 정확한 정보를 얻기 위해서는 공식문서를 볼까요?
https://developer.apple.com/documentation/coredata/#3073805
Core Data | Apple Developer Documentation
Persist or cache data on a single device, or sync data to multiple devices with CloudKit.
developer.apple.com
Persist or cache data on a single device, or sync data to multiple devices with CloudKit.
단일 장치에서 데이터를 유지 또는 캐시하거나, CloudKit을 사용하여 데이터를 여러 장치에 동기화 할 수 있도록하는 프레임워크
그렇다.. Core Data는 데이터베이스가 아니라 프레임워크였다..
코어데이터가 제공하는 기능들을 보면
- Persistence
- Core Data는 객체를 저장소에 매핑하는 세부 정보를 추상화하므로 데이터베이스를 직접 관리하지 않고도 Swift 및 Objective-C에서 데이터를 쉽게 저장할 수 있습니다.
- Undo and redo of individual and batched changes
- Core Data의 실행 취소 관리자는 변경 사항을 추적하고 개별적으로, 그룹으로 또는 한꺼번에 롤백할 수 있으므로 앱에 실행 취소 및 다시 실행 지원을 쉽게 추가할 수 있습니다.
- Background data tasks
- JSON을 개체로 구문 분석하는 등 UI를 차단할 수 있는 데이터 작업을 백그라운드에서 수행합니다. 그런 다음 결과를 캐시하거나 저장하여 서버 왕복을 줄일 수 있습니다.
- View synchronization
- Core Data는 또한 테이블 및 컬렉션 뷰에 대한 데이터 소스를 제공하여 뷰와 데이터의 동기화를 유지하는 데 도움이 됩니다.
- Versioning and migration
- 핵심 데이터에는 앱이 발전함에 따라 데이터 모델 버전을 관리하고 사용자 데이터를 마이그레이션하는 메커니즘이 포함되어 있습니다.
# 🥨 Core Data에 대한 오해와 진실
일단! CoreData는 위에서 정의 내려진 것처럼 Database에 대한 기능을 제공하는 것이지, Database가 아니다. CoreData는 Framework이다. 또한 데이터베이스, ORM(Object-Relational Mapping ,객체 관계 매핑) 등의 기능을 가진 객체 그래프 관리 프레임워크다. 즉, 근본적으로는 객체 그래프를 관리하는 역할을 하는 것이다.
객체는 알겠는데 객체 그래프는 또 무슨소리일까?
우리는 수많은 객체들이 메모리에서 관계를 형성하고 있다는 것을 알고 있다. 이것들을 그 형태 그대로 디스크에 저장할 필요가 있고 코어데이터가 바로 그것을 위한 것이기 때문에 객체 그래프 관리 프레임워크라고 불리는 것이다.
예를 들어 학생과 강의에 관련된 데이터 모델이 있다고 가정했을 때
- Student 엔터티:
- 속성: 학번, 이름, 전공
- 관계: 수강한 강의 (to-many 관계)
- Course 엔터티:
- 속성: 강의 코드, 강의명
- 관계: 수강하는 학생들 (to-many 관계)
이렇게 엔터티 간의 관계를 아래와 같이 시각적으로 나타낸 게 객체 그래프라고 한단다..!! 실제로 이렇게 저장하는지는 잘 모르겠당..
Student
├── 학번: 123
├── 이름: John
├── 전공: Computer Science
└── 수강한 강의:
├── 강의 코드: CS101
└── 강의명: Introduction to Computer Science
Course
├── 강의 코드: CS101
├── 강의명: Introduction to Computer Science
└── 수강하는 학생들:
├── 학번: 123
└── 이름: John
그리고 데이터 베이스 기능을 한다는 것도 대충 알겠는데 ORM(객체 관계 매핑)이라는 기능을 한다는 것은 또 뭘까?
coredata 기능을 구현하다 보면 알겠지만 어떠한 데이터모델의 정의할 때 entity라고 하는 데이터 유형을 추가해야 하는데 이때 각 entity를 연결하여 관계성을 맺을 수 있다. 이렇게 관계를 매핑하는 것도 결국 코어데이터에서 객체 그래프를 관리하기 위해 제공하는 기능인 것이다.
이제 대충 coredata가 어떤 역할을 하고 어떤 기능을 제공하는지 아시겠죠?? coredata가 제공하는 많은 기능들 중에서 Persistence 기능에 대해서 가장 많이 들어보고 사용해 보셨을 텐데 이 글에서 다룰 주제도 바로 Persistence입니다. 위에서 알아본 객체 그래프를 디스크에 저장하여 Persistence기능을 이용할 수 있게 되는 것인데 어떻게 우리 앱에서 데이터를 영구적으로 저장해 놓을 수 있는 것인지 천천히 알아볼까요?
# 🥨 Core Data Model
- coredata 포함한 프로젝트로 만들기
일단 프로젝트를 만들 때 coredata라는 항목을 체크하고 만들어주면 한결 수월합니다.
- coredata를 체크하지 않고 만든 프로젝트에 coredata 기능을 넣고 싶다면
xcdatamodeld 파일 생성, appDelegate에 해당 코드 추가 등 번거로우므로 Use Core Data 체크하고 프로젝트 만들기 ㅎㅎ
// MARK: - Core Data stack
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: "CoreDataPratice")
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)")
}
}
}
- Entity를 추가
add Entity 라는 버튼을 클릭해서 Entity를 추가 → Entity 이름 설정을 해줍니다. 저는 유저 정보 중 유저의 프로필에 대한 항목을 저장하고 싶으니 이름을 Profile로 바꿔줍니다.
이때 Entity는 이름, 속성, 관계 등을 포함한 객체라고 보면 되는데 뭔가 우리가 프로젝트 내에서 만들어주는 클래스나 구조체랑 비슷하죠? 이것들도 이름을 지정해 주고 내부에 프로퍼티 등을 저장해 주니까요!
- Attribute 추가
Entity를 만들었으면 addAttribute를 눌러서 그 안에 어떤 속성들을 저장해 줄 것인지 설정해 주면 됩니다. Profile이라는 Entity 내에 email, name이라는 속성들을 저장해주고 싶다면 이렇게 해주면 되겠죠? 그리고 타입까지 잘 설정해 줍니다!
이렇게 모델을 설정해 주었다면 확인해 볼 것이 있는데
- 인스펙터 창에서 codegen 확인
오른쪽에 데이터 모델 인스펙터 창을 켜보면 codegen이 Class Definition이라고 뜬다. → 이걸 확인해 주는 이유는 codegen이라는 항목이 “코드 생성(generating code)”을 어떻게 해줄 것인가에 대한 옵션 설정하는 것인데 크게 Class Definition, Category/Extension , Manual/None이 있다.
일단 공식문서 투척
https://developer.apple.com/documentation/coredata/modeling_data/generating_code
Generating Code | Apple Developer Documentation
Automatically or manually generate managed object subclasses from entities.
developer.apple.com
codegen이라는 항목 왜 필요할까요?
코어데이터와 관련한 코드를 자동으로 생성할지, 수동으로 만들어줄지를 Codegen을 통해 결정할 수 있는데요. 우리가 지금까지 Entity, Attribute 및 relationship를 정의해 줬잖아요?? 그럼 이제 Entity 인스턴스를 만드는 데 사용할 클래스를 지정해야 하기 때문에 이 항목을 설정해주어야 하는 거예요..
논리를 추가하거나 속성을 편집해야 할 때 우리는 이 두 가지 파일 (CoreDataClass.swift,CoreDataProperties.swift) 이 필요한데 codegen 옵션에 따라 이것을 관리해 줄 필요가 있을 수도, 없을 수도 있습니다.
일단 이 부분은 저도 아직 정확하게 이해하지 못해서 다른 옵션을 선택했을 때 coredata시리즈로 담에 글을 한번 써봐야 할 것 같네요 ㅎㅎ
일단 다른 옵션에 대한 공부는 제외하고 이번에는 가장 간단하게 구현해 보기 위해 두 파일 모두 자동 생성해 주는 Class Definition 옵션을 선택해서 진행해 보겠습니다.
# 🥨 NSManagerdObjectContext로 Persistent Container를 생성
프로젝트 생성해 줄 때 coredata 포함되도록 생성해주면 appDelegate에 추가적으로 코드가 생기는데 이 코드 안에서 우리가 만들어줬던 Core Data Model의 이름을 넣어서 Persistent Container를 생성해줄 수 있습니다.
저는 Model이라는 이름으로 만들어줬으니 “Model”이라고 넣어줄게요!
let container = NSPersistentContainer(name: "Model")
# 🥨 데이터 save & fetch
데이터 모델을 만들어주었다면 우리가 영구적으로 저장하고 싶은 데이터를 저장하고 싶은 entity에 저장하는 작업을 하면 되는데요?!
일단 우리가 Profile 엔터티에 저장하고 싶은 데이터를 한번 만들어볼까요? Person이라는 클래스를 만들고 이에 대한 인스턴스를 만들어줬습니다.
struct Person {
var email : String
var name : String
}
let heidi = Person(
email: "123123@gmail.com",
name: "Heidi",
)
그리고 저는 heidi의 이메일과 이름을 CoreData의 Profile에 저장하고 싶습니다. 본격적으로 어떻게 core data에 저장하고, 저장한 데이터를 불러오는지 알아봅시다.
# 🥨 Core Data에 save
저장하고 싶을 땐 이렇게 코드를 짜주면 되는데 설명 필요한 부분은 주석 걸어놓았으니 참고하면 될 것 같아요 ㅎㅎ
func saveAtcoreData() {
//core data에 저장하고 싶은 데이터..!!
let heidi = Person(name: "heidi", email: "heidi@ggg.com")
//appDelegate에서 만들었던 Persistent Container으로부터 NSManagedObjectContext를 가져옴
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext //NSPersistentContainer 타입에 viewContext라는 프로퍼티가 있어서 가능
//Entity를 가져옴 - 내가 원하는 데이터들을 어떤 Entity에 attribute로 저장하고 싶은데!
let entity = NSEntityDescription.entity(forEntityName: "Profile", in: context)
//NSManagedObject를 만든다
if let entity = entity {
let profile = NSManagedObject(entity: entity, insertInto: context)
//NSManagedObject에 값을 세팅
//앞서 지정해줬던 entity에 그 안에 생성해준 attribute이름을 "키"로 해서 저장해준다
profile.setValue(heidi.name, forKey: "name")
profile.setValue(heidi.email, forKey: "email")
do {
try context.save() //NSManagedObjectContext에 있는 save 메서드로 core data에 저장해준다
} catch {
print(error.localizedDescription)
}
}
}
# 🥨 core data에서 fetch - 저장한 데이터 불러오기
저장한 데이터는 이렇게 불러옵니다!
[Profile] 이렇게 엔터티 이름으로 타입캐스팅 되는 걸 보니 엔터티 이름이 얼마나 중요하게 작용하는지 아시겠죠??
func fetchProfile() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
do {
let profile = try context.fetch(Profile.fetchRequest()) as! [Profile]
profile.forEach {
print($0.name)
}
} catch {
print(error.localizedDescription)
}
}
이렇게 core data의 영구 저장 기능 측면에서 어떻게 데이터를 저장하고 불러오는지 알아보았는데요! 아직 완전한 지식이 아니어서 이해가 안 된 부분도 있지만 core data 시리즈를 쓰면서 코어데이터를 깊게 이해하는 시간을 가져야겠습니다!
다음 글에서는 서버 통신할 때처럼 core data에 데이터를 Create(생성), Read(읽기), Update(갱신), Delete(삭제) 관점에서 처리할 수 있는 방법과 이것을 어떻게 모듈화 시켜서 사용할 수 있는지 한번 살펴볼게요!
'iOS' 카테고리의 다른 글
[iOS] 앱의 시작지점 @main (1) | 2024.02.11 |
---|---|
[iOS] Core Data (3) | codegen(generating code) 이해해보자! (0) | 2023.12.17 |
[iOS] Core Data (2) | CRUD (1) | 2023.12.10 |
[iOS] handoff가 도대체 뭐야 | (feat. TaskManagement, NSUserActivity) (3) | 2023.12.02 |
[Swift] Bounds와 Frame에 대해 알아보자 (2) | 2023.11.18 |