안녕하세요 🙇🏻♂️
이번 포스팅에서는 Core Data에 대해 다뤄보겠읍니다!
포스팅은 편의상 편의 말투로 진행함다 😎
본문으로 들어가기 전에
iOS, 면접 단골 질문인 해당 질문을 본적이 있을 것 임
앱의 콘텐츠나 데이터 자체를 저장/보관하는 특별한 객체를 무엇이라고 하는가?
해당 답변에 나는
"데이터베이스, 그 중 UserDefaults를 사용해 보았다." 라고 답변하였는데
사실 UserDefaults 말고도 iOS에서 사용할 수 있는 데이터베이스가 많은데.
뭐가 있냐면.. SQLite, Core Data, Realm 등
SQLite와 Realm은 3rd-party 라이브러리이고 Core Data와 UserDefaults는 iOS에서 제공하는 프레임워크임 ㅋ
그래서 나는 그 중에 CoreData를 간단한 예제로 활용 해 보려고 함!
ref,
Core Data란?
Core Data는 애플의 프레임워크로, iOS 및 macOS 애플리케이션에서 데이터를 관리하고 지속화하기 위한 강력한 도구입니다. Swift는 iOS 및 macOS 앱 개발을 위해 널리 사용되는 프로그래밍 언어로, Core Data를 사용하여 데이터를 저장, 조회, 관리할 수 있습니다. Core Data는 객체 그래프 관리와 지속성(persistence)을 제공하며, 관계형 데이터베이스나 파일 시스템에 데이터를 저장할 필요 없이 객체지향적인 방식으로 데이터를 다룰 수 있게 해줍니다.
UserDefaults란?
앱의 실행에 걸쳐 지속적으로 키-값 쌍을 저장하는 사용자의 기본 데이터베이스에 대한 인터페이스.
Core Data와 UserDefaults의 차이?
보통 Core Data와 UserDefaults가 비교가 많이 됨
그 이유는 아마 둘다 3rd-party 라이브러리가 아닌 iOS 프레임워크 이기 때문이 아닐까 싶음
UserDefaults는 언제 사용하나요?
설정이나 사용자의 기본 설정과 같은 데이터의 small data chunks 를 저장하는 데 이상적이다. property list 또는 plist 로 디스크에 저장된다. property list 와 plist 는 XML 파일 형태이다.
UserDefaults 클래스는 성능 향상을 위해서 런타임에 메모리에 내용을 저장한다.
key-value 저장소일뿐이다. 이렇게하면 쉽게 액세스할 수 있지만 key-value pairs 가 서로 명시적 관계가 없음을 의미하기도 한다.
장점
- UserDefaults는 사용하기 쉽다.
- Thread safe하다. (동기화 적정 없이 어떤 쓰레드에서든 읽고 쓸 수 있다.)
- UserDefault는 앱과 앱 extensions에서 공유된다.
단점
- 동일한 키의 값을 쉽게 재정의 할 수 있습니다 (키 충돌 가능성).
- UserDefaults는 암호화되지 않습니다.
- Unit Test 시 UserDefault는 잘못된 값을 일으킬 수 있습니다.
- UserDefaults는 앱의 어느 곳에서나 전역적으로 변경 될 수 있으므로 inconsistent한 상태에서 쉽게 실행할 수 있습니다.
Core Data는 언제 사용하나요?
Core Data persistence store 의 데이터는 XML 파일 또는 SQLite 데이터베이스로 저장할 수 있다. 데이터를 메모리에 저장하거나 프로젝트 요구사항에 맞는 cutom persistent store 도 만들 수 있다.
Core Data 의 다른 주요 이점은 관계형 데이터세트 지원이다. (Xcode 에는 data model editor 가 있다.) entities 를 정의하고 entities 간의 relationship 을 정의할 수 있다.
Core Data 는 앱의 요청에 따라서 필요한 정보만 가져온다. 이것은 User Defaults 와 크게 다른 부분이다. User Defaults 클래스는 성능향상을 위해서 적절하게 변경사항을 디스크에 비동기식으로 기록한다.
장점
- CoreData는 설정 및 사용이 간단
- 'In Memory' 로 성능이 빠름
- 모든 버전이 관리되며, 한 버전에서 다른 버전으로 쉽게 마이그레이션 가능(디바이스에 이미 있는 콘텐츠를 유지하면서)
단점
- Threading 정책이 어려움
- 데이터베이스의 모든 새 object에 대해 NSManagedObject의 subclass를 사용하도록 강요됨
- 때때로 NSManagedObjectContext는 데이터베이스 변경 동기화에 문제가 있습니다.
Core Data의 주요 기능
- 데이터 모델링: Core Data는 데이터를 관리하기 위한 데이터 모델을 생성할 수 있습니다. 여기에는 엔터티(객체 모델), 속성(필드), 관계(다른 엔터티와의 연결)가 포함됩니다.
- 데이터 지속성(Persistence): Core Data는 데이터를 로컬 저장소(예: SQLite)나 메모리에 저장하고 나중에 이를 다시 불러오는 기능을 제공합니다.
- 객체 관리: Core Data는 앱의 메모리 내 객체 그래프를 관리하는 기능을 제공하며, 이를 통해 데이터를 쉽게 추가, 삭제, 업데이트할 수 있습니다.
- 쿼리 및 페칭: Core Data는 데이터를 쿼리하고 가져오기 위한 다양한 방법을 제공합니다. 이를 통해 복잡한 조건을 기반으로 데이터를 검색할 수 있습니다.
- 버전 관리: Core Data는 데이터 모델이 변경될 때 이를 관리하고 마이그레이션하는 도구를 제공합니다. 이는 앱의 데이터 모델이 업데이트될 때 발생하는 문제를 최소화할 수 있습니다.
Core Data 예제
프로젝트를 생성하는 과정중 Storage라는 선택지가 있는데 CoreData를 생성할 것인지 말 것인지 고르는 Tab이 있음
none과 Core Data가 있는데 Core Data를 선택하여 프로젝트를 생성하게 되면
이렇게 프로젝트명.xcdatamodeld 파일이 생성된다
그렇다고해서 Core Data를 선택하지않고 none을 선택했다고 Core Data를 쓰지 못하는건 아뉨
그냥 이 Data Model 파일을 다시 만들어주면 되기 때문이죠
그 이후에 자동으로 생성되는 파일 및 소스코드가 있는데 그 작업을 해주어야함
결론은 프로젝트 시작할때 체크하면 좀 편하긴함 ㅋㅋ 파일 및 예제 소스코드가 자동으로 기입되기 때문
주의 깊게 보아야 할 파일은
Persistence임
왜냐? 원래는 프로젝트가 자동으로 생성될때 만들어지는 파일이 아니기 때문
//
// Persistence.swift
// CoreData-Example
//
// Created by ukseung.dev on 8/30/24.
//
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext) // Item ??
newItem.timestamp = Date()
}
do {
try viewContext.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)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreData_Example")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
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)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
보아하니 싱글톤 패턴으로 구성되었고 이제 CoreData 사용을 위한 구조체로 보여짐
근데 Item 클래스는 뭐지 싶을거임
사실은 앞서본 .xcdatamodeld의 entities의 클래스 이름임
근데 이제 우리는 이제 entity의 이름을 바꾸고 Attributes(속성)도 바꿀거란 말이죠
인스펙터 영역에서 변경할 수 있음
그러고선 Subclass생성
그럼 이렇게
${Entity명}+CoreDataClass.swift
${Entity명}+CoreProperties.swift 가 생성이 됨
이 파일들은 엔티티에 대한 class와 extension이 자동으로 만들어짐
//
// Person+CoreDataClass.swift
// CoreData-Example
//
// Created by ukseung.dev on 9/2/24.
//
//
import Foundation
import CoreData
@objc(Person)
public class Person: NSManagedObject {
}
//
// Person+CoreDataProperties.swift
// CoreData-Example
//
// Created by ukseung.dev on 9/2/24.
//
//
import Foundation
import CoreData
extension Person {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Person> {
return NSFetchRequest<Person>(entityName: "Person")
}
@NSManaged public var id: String?
@NSManaged public var name: String?
@NSManaged public var updateDate: Date?
}
extension Person : Identifiable {
}
그리고 나선 예제 코드를 작성해줌
//
// ContentView.swift
// CoreData-Example
//
// Created by ukseung.dev on 8/30/24.
//
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Person.updateDate!, ascending: true)],
animation: .default)
private var items: FetchedResults<Person>
@State var name: String = ""
var body: some View {
NavigationView {
VStack {
TextField("Placeholder", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
List {
ForEach(items) { item in
NavigationLink {
VStack {
Text("id => \(item.id ?? "nil")")
Text("name => \(item.name ?? "nil")")
Text("updateDate => \(item.updateDate ?? Date(), formatter: itemFormatter)")
}
} label: {
Text("데이터 저장 일시 \(item.updateDate!, formatter: itemFormatter)")
}
}
.onDelete(perform: deleteItems)
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func generateID() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMddHHmmss"
let dateString = dateFormatter.string(from: Date())
return dateString
}
/// Core Data CRUD중 Create
private func addItem() {
withAnimation {
let newItem = Person(context: viewContext)
newItem.id = generateID()
newItem.updateDate = Date()
newItem.name = name
do {
try viewContext.save()
name = ""
} 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)")
}
}
}
/// Core Data CRUD중 Delete
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.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)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
#Preview {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
결과물
적재된 데이터 확인
1. Window > Device and Simulators
2. Donwload Container...
3. xcappdata를 우클릭하여 '패키지 내용 보기'
4. AppData > Library > Application Support > Mobile.sqlite
해당 파일을 DB Browser for SQLite 프로그램에 넣어야 함
다운로드 링크
5. 확인 완료
Excute SQL 탭에 들어가면 SQL 문으로도 DB 조회가 가능함!
결론
iOS FrameWork인 CoreData를 살펴보았고 프로젝트 상황에 따라 적용할 라이브러리 및 프레임 워크를 결정하여 기능 구현을 하면 될듯함
소스코드
'iOS > Swift' 카테고리의 다른 글
[Swift] Realm 기본 개념 및 간단 예제 (3) | 2024.09.06 |
---|---|
[Swift] Tuist CLI, 나도 한번 써보자 (0) | 2024.08.13 |
[Swift] Framework,SDK 생성 및 import 하는 방법 (1) | 2024.08.06 |
[Swift] Storyboard없이 CodeBaseUI 코드 구현 (0) | 2024.05.31 |
[Swift] 앱 빌드 오류, failed: Operation not permitted (1) (2) | 2023.11.29 |