기술스택 알아보기 두 번째 : Core Data
Framework
코어 데이터는 데이터 베이스가 아니다. 프레임워크다.
Use Core Data to save your application’s permanent data for offline use, to cache temporary data, and to add undo functionality to your app on a single device.
코어 데이터를 사용하여, 오프라인 사용을 위해 영구적인 데이터를 저장하고 단일 디바이스의 앱에 실행 취소 기능을 추가 할 수 있다.
Through Core Data’s Data Model editor, you define your data’s types and relationships, and generate respective class definitions. Core Data can then manage object instances at runtime to provide the following features.
코어 데이터의 데이터 모델 에디터를 통해, 데이터의 유형과 관계를 정의하고 각 클래스의 정의를 생성한다.
코어 데이터는 런타임 개체 인스턴스를 관리하여 다음과 같은 기능을 제공할 수 있다.
Core Data abstracts the details of mapping your objects to a store, making it easy to save data from Swift and Objective-C without administering a database directly.
코어 데이터는 개체를 저장소에 매핑하는 세부 정보를 추상화하므로, 데이터 베이스를 직접 관리하지 않고도 Swift 및 Objective-C 에서 데이터를 쉽게 저장할 수 있다.
SQL 언어를 기반으로 만든 데이터 베이스가 존재하는데, 그 중 하나의 방법 이다.
SQLite는 모바일 환경에 최적화된 데이터 베이스이다.
키(Key)와 값(Value)들의 간단한 관계를 테이블화 시킨 매우 간단한 원칙의 전산정보 데이터베이스이다.
Core Data의 기능 중 하나 인 Persistence는 관계형 데이터베이스인 SQLite에 의해 지원된다.
하지만 여기서 주의해야할 점은 Persistence는 Core Data의 기능 중 하나 일 뿐, Core Data == Database가 아니다.
Persistence 기능을 사용하지 않고도 Core Data를 사용할 수 있다. 기억해야할 것은 Core Data는 Database가 아니고, 데이터를 유지하기 위한 API도 아니다.
그저 앱의 모델 계층이며, 객체 그래프를 관리하는 Framework이다.
Core Data’s undo manager tracks changes and can roll them back individually, in groups, or all at once, making it easy to add undo and redo support to your app.
코어 데이터의 실행 취소 관리자는 변경 사항을 추적하여 개별적, 그룹, 또는 모두 한번에 롤백할 수 있으므로 앱에 데이터 추가, 삭제 등을 쉽게 할 수 있다.
Perform potentially UI-blocking data tasks, like parsing JSON into objects, in the background. You can then cache or store the results to reduce server roundtrips.
백그라운드에서 JSON 오브젝트로 파싱하는 것과 같은 UI 블로킹 데이터 작업을 수행한다.
그렇게 되면, 결과를 캐시하거나 저장하여 서버 라운드 트립(왕복 지연시간)을 줄일 수 있다.
Core Data also helps keep your views and data synchronized by providing data sources for table and collection views.
코어 데이터는 또한 테이블 뷰 및 컬렉션 뷰에 대한 데이터 소스를 제공하여 뷰와 데이터를 동기화하는데 도움이 된다.
Core Data includes mechanisms for versioning your data model and migrating user data as your app evolves.
코어 데이터는 앱이 점점 버전이 업그레이드됨에 따라 데이터 모델을 버전화 하고, 사용자 데이터를 마이그레이션 하는 메커니즘이 포함되어 있다.
Core Data의 Entity의 인스펙터를 보면 이러한 화면이 나온다.
위 3개의 옵션들은 각각의 기능을 제공한다.
Class Definition
이러한 경우에 사용하면 되는 옵션이다. 그리고 이 특징 때문에
let contact = Contact()
위 코드가 돌아갈 수 있는 것이다.
Categort/Extension
Manual/None
Core Data Stack은 Core Data의 핵심이다.
Core Data Stack은 앱의 모델 layer를 관리하고 유지하는 역할을 한다.
위 그림에서도 볼 수 있듯
NSManagedObjectModel
NSManagedObjectContext
NSPersistenStoreCoordinator
그리고
NSPersistentContainer
로 이루어져 있다.
하나씩 살펴보자.
Entity를 설명하는 Database 스키마라고 보면 된다.
managed object의 structure를 정의한다.
기능 중 하나인듯 하다.
Persistent Storage(영구 저장소) 와 managed object model을 연결한다.
애플 공식 문서 설명
A coordinator that uses the model to help contexts and persistent stores communicate.
모델을 사용하여 context와 영구저장소와의 통신을 지원하는 코디네이터이다.
이 그림을 보면 중간에 Persistent Store Coordinator 가 있다.
중간에 연결 역할을 해주고 있는게 보인다.
Transaction이라고 보면 된다.
managed object를 생성하고, 저장하고, 가져오는(fetch)작업같은 걸 제공한다.
애플 공식 문서 설명
An object space to manipulate and track changes to managed objects.
변경되는 오브젝트의 변경 내용을 조작하고 추척할 개체 공간이다.
이정도 확인을 해보고! 직접 코드로 적용한 것을 살펴보자.
static var shared: CoreDataWorker = CoreDataWorker()
lazy var feedArrayContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: {
(data, error) in
if let error = error as NSError? {
fatalError("Error")
}
})
return container
}()
Core Data Stack에서 설명한 계층 중 가장 위에 있는 것이다.
NSPerisistenContainer
는 Core Data Stack
을 나타내는 필요한 모든 객체를 포함한다 라고 했다. 따라서 Core Data Stack
을 사용하기 위해 이렇게 Model
로 지어진 Entity
를 불러온 것이다.
var feedContext: NSManagedObjectContext {
return feedArrayContainer.viewContext
}
그 다음 코드는 NSManagedObjectContext
를 가져오는 코드이다.
NSManagedObjectContext는 managed Object를 생성하고, 저장하고, 가져오는 작업을 제공한다.
그러니까 CRUD 작업을 위해서는 NSManagedObjectContext 로 파일을 가져와 사용해야 한다라는 뜻이다.
따라서 가져왔다.
func saveFeed() {
// hasChanges : 해당 Context가 바뀌었는지 체크
if feedContext.hasChanges {
// perform : ContextQueue에서 지정된 블록을 비동기적으로 수행한다.
feedContext.perform {
do {
// save : 저장되지 않은 변경 사항을 등록된 개체에 대해 Context의 저장소에 저장한다.
try self.feedContext.save()
} catch {
fatalError(error.localizedDescription)
}
}
}
}
위 코드는 저장하는 코드를 따로 메서드로 파서 코드의 중복을 막았다.
코드를 간단히 살펴보자면, hasChanges
로 데이터가 바뀌었는지 체크하고,
perform
으로 비동기적 수행을 진행한다.
만약 저장되지 않았던 정보라면, 따로 저장을 해주는 save()
메서드를 호출하게 된다.
이제는 CRUD 정의 코드이다.
func insert(userImage: Data, userName: String, text: String, like: Int, uploadImage: Data, time: Date, completion: ((_ error: NSError?) -> Void)? = nil) {
let feedArray = FeedArray(context: feedContext)
feedArray.userImage = userImage
feedArray.userName = userName
feedArray.text = text
feedArray.like = Int16(like)
feedArray.uploadImage = uploadImage
feedArray.time = time
do {
// validateForInsert : managed object를 현재 상태로 append할 수 있는지 여부를 결정한다.
try feedArray.validateForInsert()
// 에러 발생시
} catch let error as NSError {
// rollback : 모든 항목을 제거하고 모든 append 및 delete를 취소하고 업데이트 된 오브젝트 또한 마지막으로 올려진 값으로 복원한다.
feedArray.managedObjectContext?.rollback()
completion?(error)
return
}
// 그게 아니면 저장하는 방법을 택한다.
saveFeed()
completion?(nil)
}
정석적인 방법은 아니라고 생각한다.
하지만, Core Data 자체가 데이터 베이스가 아니니 구현하는 방법은 다양하다고 생각한다.
따라서 필자는 이렇게 구현하였다.
func read() -> [FeedArray] {
// NSFetchRequest : persistent store 에서 데이터를 검색하는 데 사용되는 검색 기준에 대한 정의가 담겨있는 클래스이다.
// fetchRequest : 비동기적으로 실행되는 기본적인 가져오기 요청이다.
let readRequest: NSFetchRequest<FeedArray> = FeedArray.fetchRequest()
var feedList = [FeedArray]()
// Sorting Function
// NSSortDescriptor : 모든 오브젝트에 공통으로 적용되는 컬렉션을 적용하는 방법에 대한 클래스.
// 특히 Sorting 과 관련된 기능들이 정의 되어있다.
let sort = NSSortDescriptor(key: #keyPath(FeedArray.time), ascending: false)
readRequest.sortDescriptors = [sort]
// 블록 안의 작업이 Context에 대한 올바른 대기열에서 실행되는지 확인한다.
feedContext.performAndWait {
do {
// fetch : 위에서 정렬된 Entity를 기준으로 지정된 항목의 배열을 반환한다.
feedList = try feedContext.fetch(readRequest)
} catch {
fatalError(error.localizedDescription)
}
}
return feedList
}
read는 NSFetchRequest
를 통해 비동기적으로 데이터를 가져오는 방법을 사용하였다.
또한 중간에 NSSortDescriptor
를 사용하여 시간순 정렬을 하였다.
func update(_ feedArray: FeedArray, userImage: Data, userName: String, text: String, like: Int, uploadImage: Data, time: Date, completion: (() -> Void)? = nil) {
feedArray.userImage = userImage
feedArray.userName = userName
feedArray.text = text
feedArray.like = Int16(like)
feedArray.uploadImage = uploadImage
feedArray.time = time
saveFeed()
completion?()
}
update는 다른 코드에 비해 상대적으로 간단하다.
이유는 그저 바뀐 정보들만 다시 save하면 되는 것이기에 코드가 간단했다.
func delete(_ feedArray: FeedArray) {
// delete : 변경사항을 저장할 때 영구 저장소에서 제거해야 하는 오브젝트를 지정한다.
feedContext.delete(feedArray)
saveFeed()
}
delete 부분은 coreData에서 제공하는 delete로 해당 배열을 삭제하면 된다.
이렇게 coreData를 사용해보았다.
이론이 많이 빡세지만, 막상 사용할때는 생각보다 쉬웠다.