Core Data

서론

아이폰 앱에서 데이터를 불러오는 데에는 많은 방법이 있습니다. 일반적인 방법으로 DB Server에 있는 DataBase에 접속하여 데이터를 끌어오는 방법을 생각할 수 있고, 요새는 특히 클라우드가 활성화되면서 AWS에 DB를 올려서 데이터를 불러올 수도 있습니다. 하지만 저와 같이 규모가 크지 않다면 Firebase를 이용하여 데이터를 클라우드 위에 올려서 가져올 수 있겠죠. 하지만 오늘 다룰 이야기는 전혀 다른 이야기입니다. 굳이 사용자들이 같은 데이터 베이스를 바라볼 필요가 없다면?

메모 앱이라든지, 사진 앱이라든지 저희가 데이터를 서버 혹은 클라우드에 올리던가요? 아니죠. 저희 핸드폰에 저장합니다.

서버를 통하지 않고, 각 iPhone의 저장소에 데이터를 저장하는 방법을 알아보도록 하겠습니다.

Core Data Persist or cache data on a single device, or sync data to multiple devices with CloudKit.

  • 버전 정보
    • iOS 3.0+
    • iPadOS 3.0+
    • macOS 10.4+
    • watchOS 2.0+

버전 정보를 보면, 거의 초창기 기기들도 사용할 수 있다는 것을 알 수 있네요!

제가 사용한 방법으론 3가지가 있습니다. (더 있을 수도 있겠죠,,?)

  • viewContext.fetch()
  • @FetchedObject
  • PersistenceController

사실 위 코드들을 사용하기 전에, Core Data 사용을 위해 프로젝트에 Core Data를 추가하고, Entity를 설정하는 등 초기 작업들은 생략하고, Core Data를 통해 데이터를 불러오는 코드만 비교하도록 하겠습니다.

본론

viewContext.fetch

기본적으로 viewContext를 선언한 후, 저장된 데이터 타입에 맞게 Request를 생성하고, viewContext에서 해당 타입에 알맞은 데이터를 불러오는 과정을 아래 코드에서 확인할 수 있습니다.

아래 코드는 제가 직접 생성했던 Vocabulary 라는 타입의 구조체 입니다.

@Environment(\.managedObjectContext) private var viewContext

(...)

func getVocabularyData()
-> [Vocabulary] {
	// fetch를 위한 Request 생성
	let vocabularyFetch = Vocabulary.fetchRequest() 
	// 생성한 Request를 실행한 후, 결과값을 results에 담기
	let results = (try? self.viewContext.fetch(vocabularyFetch) as [Vocabulary]) ?? []
	self.vocabularyList = results
	return results
}

이렇게 담아온 results를 Store에 담아 이를 EnvironmentObject로 사용하면, 뷰에서 자유롭게 Core Data에서 꺼내온 데이터를 사용할 수 있게 되겠죠?

@FetchRequest

앞에 @ 가 붙었으니, Property Wrapper 네요. 먼저 공식 문서에서 설명하는 걸 확인해 볼까요?

  • 버전 정보
    • iOS 13+

A property wrapper type that retrieves entities from a Core Data persistent store. Core Data persistent store라는 공간에서 entity들을 찾는 property wrapper.

앞서 Entity는 본 적이 있지만, Core Data persistent store는 초면이네요!

PersistenceController

struct PersistenceController {
    static let shared = PersistenceController()
	
	(...)
	
	let container: NSPersistentContainer
	
	init(inMemory: Bool = false) {
	        container = NSPersistentContainer(name: "GGomVoca")
	        if inMemory {
	            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
	        }
	        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
	            if let error = error as NSError? {
	                fatalError("Unresolved error \(error), \(error.userInfo)")
	            }
	        })
	        container.viewContext.automaticallyMergesChangesFromParent = true
	    }
	    
	    (...)
	    
	}

결론

References

  • https://betterprogramming.pub/core-data-building-a-custom-store-84d19f39dec4