일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- state
- firebase
- Performance
- iphone
- stateobject
- 접근성제어
- WWDC
- view
- 달력
- CS
- auth
- Network
- RxSwift
- ios
- arkit
- 네트워크
- UIKit
- combine
- gesture
- withAnimation
- swift
- 최적화
- Concurrency
- toolbarvisibility
- Animation
- dataflow
- GCD
- SwiftUI
- authentication
- avsession
- Today
- Total
XLOG
[Swift] CoreData 본문
보통 앱 내의 데이터를 저장할 때 많이 사용하는 것이 UserDefault 와 CoreData 이다.
UserDefault 의 경우 앱의 설정(라이트모드, 다크모드 등) 과 같은 간단한 데이터를 저장하는데 사용한다면, CoreData 의 경우 좀 더 복잡한 데이터를 저장하는데 사용한다고 한다.
처음 Swift 를 접하고 코어데이터란게 있다 라는 얘기를 주변사람들에게 들었을 때는 DB 같은건가 하는 생각을 했었다.
하지만 CoreData 는 단순히 데이터베이스라고 생각하긴 어렵다.
CoreData 는 프레임워크다. 어플리케이션에서 offline으로 사용할 수 있는 영구적인 데이터를 저장, 관리를 도와주는 프레임 워크로 icloud 계정을 통해 다양한 device 에 데이터를 동기화까지 가능하게 해준다.
공식문서의 CoreData Stack 을 보면 Persistent Container 는 Model, Context, Store coordinator 로 구성이 되어 있는걸 볼 수 있다. Model 은 우리가 알고있는 데이터의 스키마로 생각을 하면 된다. Store Coordinator 는 저장소, 그리고 Context 는 일종의 Transaction으로 NSManagedObjectModel 을 CRUD 를 해준다. 위에 세 요소를 연결해주는 역할을 Persistent Container 가 해준다.
가볍게 CoreData에 대한 설명이 끝났으니 이제 사용법에 대해 알아보자.
1. NSPersistenceContainer 생성
2. 생성한 container 를 통해 persistence Store 를 load 하여 영구저장소와 연결
3. container의 viewContext 를 통해 fetch, 혹은 NSManagedObjectModel 을 생성, 변경, 삭제 등의 작업 진행
4. 변경사항을 저장
의 순서로 진행이 된다.
우선 프로젝트를 생성할 때 storage 에서 CoreData 를 생성하거나
새로운 파일을 만들 때 DataModel 을 만들면 된다. 그러면 프로젝트 내부에 filename.xcdatamodeld 의 파일이 보일 것이다.
이전 DiffableDataSource 설명에 사용했던 TodoList 프로젝트에 CoreData를 연결해 볼 것이기에, 그 때 정의한 Todo 구조체 와 같은 형태의 프로퍼티를 가지는 Todo Entity 를 생성하였다.
그후 NSManagedObject 를 자동으로 생성하게되면
해당 파일이 생성되고, 내부를 살펴보면
이렇게 자동으로 Todo 객체가 생성된다. 여기서 unwrappedTitle, unwrappedDeadline 은 추가한 내용이다. 이제 준비물이 완료 되었으니 PersistentContainer 를 생성해보자.
import CoreData
final class PersistenceManager {
static let shared = PersistenceManager()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "TodoListWithUIKit")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as? NSError {
fatalError("\(error)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
이 container 내부에 viewContext(transaction) 을 사용해서 NSManagedObject를 생성, 수정 등 관리가 가능하다.
func fetchTodos() {
let request = Todo.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \Todo.deadline, ascending: false)]
do {
self.todos = try PersistenceManager.shared.container.viewContext.fetch(request)
} catch {
print(error)
}
updateView()
}
이전 TodoList 프로젝트에 ViewModel 에서 사용했던 [Todo] 에 CoreData를 연결해서 사용하기 위한 fetch 함수 이다.
위에 보여진 Todo 객체에 정의된 fetchRequest를 통해 NSFetchRequest 를 생성하고, 해당 request를 이용하여 viewContext로 원하는 request를 불러올 수 있다.
원하는 데이터를 추가, 수정, 삭제 시
func addTodo() {
// 해당 Context에 Todo 객체를 생성
// Transaction 엔 이미 새로운 Todo가 생성되음
var todo = Todo(context: PersistenceManager.shared.container.viewContext)
todo.id = UUID().uuidString
todo.title = "title"
todo.deadline = Date()
todo.isCompleted = false
do {
// 변화된 Transaction을 PersistenceStore 에 저장
try PersistenceManager.shared.container.viewContext.save()
} catch {
print(error)
}
}
func updateTodo(_ selectedTodo: Todo) {
var todo = selectedTodo
todo.title = "Change Title"
...
do {
try PersistenceManager.shared.container.viewContext.save()
} catch {
print(error)
}
}
func deleteTodo(_ deleteTodo: Todo) {
PersistenceManager.shared.container.viewContext.delete(todo)
}
문제점
CoreData를 연결한 후 프로젝트에서 Todo 객체를 Update 하여 데이터의 변경을 확인했지만, snapShopt 에 이를 적용을 . 할경우 update 된 타이틀을 반영하지 못했다.
func updateView() {
let filtered = self.todos.filter({ !$0.isCompleted })
print("filtered todo list>>>>>>>>>>>>>>>>>>>>>")
for todo in filtered {
print(todo.unwrappedTitle)
}
var snapshopt = NSDiffableDataSourceSnapshot<CollectionViewSection, Todo>()
snapshopt.appendSections([.main])
snapshopt.appendItems(filtered)
self.dataSource.apply(snapshopt, animatingDifferences: true)
}
그래서 편법으로 snapshot의 배열을 비운 다음 다시 적용을 하여 변화를 두 번 주는 편법을 사용했다..사실 이렇게 된다면 비효율적이다. DiffableDataSource 의 장점은 변화를 캐치하여 효율적으로 그 변화에 대한 redraw를 해주는 것으로 파악하고 있는데. 이렇게 되면 모든 데이터를 다시 그려주게 된다고 생각한다. 이 부분은 시뮬레이터에서만 나타나는 문제인지, 아님 코드의 문제가 있는 것인지 조금 더 확인을 해봐야 할 것 같다..
임시 방편 코드
func updateView() {
let temp = NSDiffableDataSourceSnapshot<CollectionViewSection, Todo>()
self.dataSource.apply(temp, animatingDifferences: false)
let filtered = self.todos.filter({ !$0.isCompleted })
print("filtered todo list>>>>>>>>>>>>>>>>>>>>>")
for todo in filtered {
print(todo.unwrappedTitle)
}
var snapshopt = NSDiffableDataSourceSnapshot<CollectionViewSection, Todo>()
snapshopt.appendSections([.main])
snapshopt.appendItems(filtered)
self.dataSource.apply(snapshopt, animatingDifferences: true)
}
'Swift' 카테고리의 다른 글
실시간 STT (Sound to Text) 구현하기 (1) | 2024.12.22 |
---|---|
Fatal error: Duplicate keys of type 'Node' were found in a Dictionary 해결 (0) | 2024.08.16 |
[Swift] IAP, 인앱결제 StoreKit2 (2) | 2024.05.19 |
[Swift] Concurrency (0) | 2024.05.19 |