코어데이터(Core Data)란 macOS 및 iOS 운영 체제에서 Apple이 제공하는 객체 그래프 및 지속성 프레임워크이다.
UserDefaults는 앱의 설정과 같은 간단한 데이터를 저장하기에 적합한 반면, 코어데이터는 복잡하고 큰 데이터를 저장하기에 적합하다.
새롭게 프로젝트를 생성한다면 Use Core Data를 체크한다. 반대로 기존 프로젝트에 추가한다면 Data Model 파일을 추가해야 한다. 그리고 Bookmark Entity에 관련 정보를 저장한다. 공지에서 북마크 버튼을 클릭하면 여기에 저장한다. 북마크는 Notice 모델이므로 Attribute에 title, time, url을 추가한다.
struct Notice: Codable, Equatable {
let title: String
let time: String
let url: String
}
Core Data Stack은 Model, Context, Store coordinator, 그리고 Persistent container로 이루어져 있다.
코어데이터를 관리하는 CoreDataManager를 만들어보자. 프로젝트에 CoreDataManager 파일을 생성한다.
Persistent container를 생성한다. "Model" 위치에는 Data Model 파일명을 넣는다. 프로젝트에서 CoreDataManager를 한번만 생성하고 싶으므로 shared를 정의한다.
// CoreDataManager.swift
import CoreData
import Foundation
class CoreDataManager {
static var shared: CoreDataManager = CoreDataManager()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
}
context와 북마크 Entity를 가져온다.
class CoreDataManager {
...
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
var bookmarkEntity: NSEntityDescription? {
return NSEntityDescription.entity(forEntityName: "Bookmark", in: context)
}
}
북마크 Entity로부터 managedObject를 생성한다. Bookmark는 title, time, url을 가지고 있으므로 오브젝트에 각각 해당 값들을 세팅한다. insertBookmark라는 함수는 notice를 받아 각각 title, time, url를 세팅한다.
class CoreDataManager {
...
func insertBookmark(_ notice: Notice) {
if let entity = bookmarkEntity {
let managedObject = NSManagedObject(entity: entity, insertInto: context)
managedObject.setValue(notice.title, forKey: "title")
managedObject.setValue(notice.time, forKey: "time")
managedObject.setValue(notice.url, forKey: "url")
}
}
}
managedObject를 세팅한 이후 context를 저장하는 saveToContext 함수를 이용해 저장한다.
class CoreDataManager {
...
func saveToContext() {
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func insertBookmark(_ notice: Notice) {
if let entity = bookmarkEntity {
let managedObject = NSManagedObject(entity: entity, insertInto: context)
managedObject.setValue(notice.title, forKey: "title")
managedObject.setValue(notice.time, forKey: "time")
managedObject.setValue(notice.url, forKey: "url")
saveToContext()
}
}
}
Create 함수는 위에서 insertBookmark 함수로 구현했으니, 나머지 Read, Update, Delete 기능을 구현해본다.
fetchBookmarks 함수는 bookmark에 저장된 데이터를 fetch하는 함수이다. 그리고 getBookmarks 함수는 북마크들을 notice 모델로 변환해 notice 배열을 반환하는 함수이다.
class CoreDataManager {
...
func fetchBookmarks() -> [Bookmark] {
do {
let request = Bookmark.fetchRequest()
let results = try context.fetch(request)
return results
} catch {
print(error.localizedDescription)
}
return []
}
func getBookmarks() -> [Notice] {
var notices: [Notice] = []
let fetchResults = fetchBookmarks()
for result in fetchResults {
let notice = Notice(title: result.title ?? "", time: result.time ?? "", url: result.url ?? "")
notices.append(notice)
}
return notices
}
}
updateBookmark함수는 notice를 받아 제목을 수정하는 함수이다. 사용하지 않지만 Update함수를 구현하기 위해 만들었다. 이 함수에서 수정할 title, time, url을 옵셔널로 받아서 수정하면 좋겠다.
class CoreDataManager {
...
func updateBookmark(_ notice: Notice) {
let fetchResults = fetchBookmarks()
for result in fetchResults {
if result.url == notice.url {
result.title = "업데이트한 제목"
}
}
saveToContext()
}
}
deleteBookmark 함수는 notice를 받아서 코어데이터에 해당 notice를 삭제하는 함수이다. notice의 url은 고유의 값이므로, filter를 사용하여 해당 notice를 찾아 삭제하였다.
deleteAllBookmarks 함수는 코어데이터에 있는 북마크 관련 정보들을 다 삭제하는 함수이다. 두 함수 모두, 값을 변경한 이후에 context를 저장한다.
class CoreDataManager {
...
func deleteBookmark(_ notice: Notice) {
let fetchResults = fetchBookmarks()
let notice = fetchResults.filter({ $0.url == notice.url })[0]
context.delete(notice)
saveToContext()
}
func deleteAllBookmarks() {
let fetchResults = fetchBookmarks()
for result in fetchResults {
context.delete(result)
}
saveToContext()
}
}
처음에 UserDefaults로 구현했다가(링크는 밑에 첨부), 위의 데이터를 저장하기에 더 적합한 코어데이터로 변경하게 되었다. 처음 써봤는데, 하루 고생해서 구현을 하게 되어 매우 뿌듯하다😆
혹시 이 부분에서 만약 찾는 결과가 없을 경우 런타임 에러가 날 가능성이 있을까요?
let notice = fetchResults.filter({ $0.url == notice.url })[0][0] 이 부분이요