CoreData & CloudKit Overview

Isaac·2022년 7월 25일
5

들어가기 전에

Core Data ? CloudKit ? 그거 누가 쓰는데 ? 현업에서 아무도 안쓴다고 하던데 ?

아마 많은 분들이 들어보셨을 이야기라고 생각합니다. 그리고 일부 맞는 이야기이기도 합니다. 내부 DB로는 Realm을 사용하거나 fmdb 혹은 SQLite.swift 와 같은 SQL wrapper 를 많이 사용하기도 하고 동기화가 필요한 경우에는 Firebase를 활용해서 기능을 확장 하기도 합니다.

하지만 1st-party 프레임워크를 사용하면서 누릴 수 있는 공식지원이라는 안정적인 개발환경이나 간단한 코드로 iOS는 물론이고 iPadOS, MacOS, watchOS 까지 폭넓은 애플 생태계에서 동시에 개발이 가능하다는 장점은 다른 어떤 프레임워크에서도 찾을 수 없는 독보적인 기능입니다. 특히 생산성앱 분야에서는 인디 개발자는 물론 이미 많이 성장한 회사들도 동기화 기능으로 iCloud를 사용하는 경우가 많이 있습니다. 대표적으로 마크다운 기반의 노트앱 bear 이나, iPad 필기앱 최강자 goodnotes 역시 iCloud와 Core Data를 적극적으로 활용하고 있습니다.

우리가 기획하는 모든 앱들의 백엔드를 CloudKit이나 Core Data가 대체할 수 있다는 이야기는 절대 아닙니다. 당장 아래에서 설명할 때도 적합하지 않은 경우들에 대해서 이야기 할 예정이고요. 하지만 반대로 이 기술이 힘을 발휘하는 상황도 확실히 있기 때문에, 오늘 잠시 시간을 투자해서 이 기술의 특징이나 장점들을 알아둔다면 우리가 해결할 수 있는 문제의 폭이 더 넓어질 것입니다.

그럼 이제 하나씩 주제를 들어가보도록 할까요?

이번 글은 Overview 이기 때문에 빠르게 개념만 짚고 넘어가보려고 합니다 🙂

CoreData

Intro

Core Data는 Object Graph를 관리합니다.

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

주로 데이터베이스처럼 쓰고 있기는 하지만, 사실 Core Data는 Obejct Graph를 관리하기 위한 애플의 1st party 프레임워크입니다. Object Graph라는 이름처럼 Object사이의 관계를 관리할 필요가 있는 다양한 곳에서 다양한 모습으로 사용이 가능합니다.

위에 인용한 Core Data 공식 문서의 한줄 설명에도 나와 있는것처럼 Core Data는 한 기기에서의 캐시를 관리하는데도 사용이되고, sqlite를 사용해서 데이터베이스처럼 사용하기도 합니다. 그리고 오늘의 주제인 CloudKit을 활용해서 여러개의 기기에서의 데이터 동기화에도 사용할 수 있습니다.

Core Data & SQLite

Core Data는 SQLite라고 들었는데…

맞기도 하고, 틀리기도 합니다. Core Data가 제일 많이 사용되는 사례가 디바이스 내부에 앱의 데이터를 저장하는 것이고, Core Data에서 디바이스에 Object Graph를 저장하기 위해서 사용하는 방법중 하나가 SQLite이기 때문입니다. 저장을 하는 방법을 sqlite를 사용하고, 용어들도 비슷한 부분이 많기 때문에 RDB 특히 SQL에 익숙하신 분들은 Core Data에 쉽게 적응하실 수 있을것입니다. 하지만 처음에도 설명한 것 처럼 Core Data의 주된 기능은 Object Graph를 관리하는 일이고, 그 기능을 구현하기 위해서 sqlite를 사용하는 것이기 때문에 SQL의 기능을 쉽게 사용하는 것이 목적이라면 Core Data가 적절한 선택이 아닐 수도 있습니다.

한가지 예로, 대량의 데이터를 다룰 때 퍼포먼스 이슈가 생길 수 있습니다. Core Data는 Object Graph를 메모리에 올려놓고 사용합니다. Object를 지우거나 수정하는 등의 작업을 하려고 하면 우선 메모리에 불러와야 합니다. 만약 수천개의 Object를 한번에 지우거나 하는 등의 작업을 빈번하게 해야 한다면 우선 그 모든 Object를 메모리에 먼저 올려야 하기 때문에 퍼포먼스 이슈가 생길 수 있습니다.

그래서 Core Data는 언제 쓰나?

우리 앱이 여러가지 관계들이 표현되어야 하는 Object들을 다루고 있다면 Core Data를 고려해볼 수 있습니다. 특히 아래에서 다룰 CloudKit과 함께 사용 되면 다양한 분야에서 개발자에게 꽤나 강력한 기능들을 제공해줍니다. 하지만 단순하게 SQL의 wrapper가 필요한 경우라면 시작에 언급되었던 fmdb 혹은 SQLite.swift 가 더 좋은 옵션이 될 수 있습니다. 특히 Core Data에는 JOIN 테이블을 만드는 쿼리가 존재하지 않기 때문에 이를 고려한 데이터 모델링이 필요한 경우에는 다른 옵션들을 선택해야 합니다.

Core Data Stack

Core Data framework 의 Core Data Stack은 우리 App에서 Model 레이어를 담당하기 위해 설계 되었습니다. 즉, MVVM, MVC, MVP 등 Model 레이어를 포함하는 여러가지 디자인 패턴에서 Model의 역할을 담당할 수 있다는 이야기입니다. Core Data Stack은 아래의 그림과 같이 도식화될 수 있습니다.

이제 각각의 객체들이 어떤 역할을 하고, 어떻게 Core Data Stack이 동작하는지 알아보도록 합시다

Model - NSManagedObjectModel

NSManagedObjectModel 은 앱에서 사용되는 object type(Entity), 가질 수 있는 속성(Attribute), 관계(Relation)를 나타내는데 사용됩니다. 이 개념 자체는 SQL의 스키마와 비슷해 보이지만 Core Data Stack에서 sqlite를 사용하는 경우에만 스키마를 사용합니다. 반복해서 이야기 하지만 Core Data는 데이터베이스가 아니기 때문에 sqlite 이외에도 많은 종류의 영구적 저장소를 사용합니다.

Store Coordinator - NSPersistentStoreCoordinator

Store CoordinatorNSManagedObjectModel 과 실제 데이터가 읽고 써지는 NSPersistentStore 의 연결고리 역할을 합니다. NSManagedObjectModel 을 이용해서 Entity를 만들고, 이미 존재한다면 NSPersistentStore 로부터 데이터를 로드합니다.

NSPersistentStoreCoordinator 에서는 실제 데이터에 대한 입출력이 이뤄지게 되며, NSManagedObjectContext 의 요청에 대한 응답 등의 작업을 수행합니다.

참고로, Core Data에서 NSPersistentStore 는 기본적으로 4가지 유형의 Store를 제공합니다.

  1. NSSQLiteStoreType : Default 타입으로 지정되어 있으며, 실제로 가장 많이 사용됩니다. SQLite데이터베이스를 통해서 지원됩니다.
  2. NSXMLStoreType : XML파일로 지원됩니다. 읽기 쉽지만 파일 전체를 불러와야 하는 atomic 저장소로 메모리가 많이 사용될 수 있습니다. MacOS에서 사용됩니다.
  3. NSBinaryStoreType : NSXMLStoreType 과 동일한 atomic 저장소로 바이너리 파일로 지원됩니다.
  4. NSInMemoryStoreType : in-memory로 지원되며, 앱이 종료되거나 디바이스가 꺼지면 데이터가 삭제됩니다. Unit test 혹은 데이터 캐싱에 사용되기도 합니다.

Context - NSManagedObjectContext

실제로 코드를 작성하게 되면 제일 많이 마주치게 되는 객체입니다. 앱의 타입들에 대한 변화들을 추적하여 관리 중인 데이터들에 대한 Seamless한 접근을 지원합니다. Context를 통해서 NSPersistentStoreCoordinator 에게 데이터의 CRUD를 요청할 수 있습니다. NSManagedContext 는 개발하면서 제일 많이 사용되는 클래스이기 때문에 간단한 예시 코드와 함께 몇가지 정보들을 남겨둡니다.

// from WWDC19 - Making Apps with Core Data
// (https://developer.apple.com/wwdc19/230)
context.perform {
	let post = Post(context: context)
	post.title = "Hello World"
  try? context.save()
}
  • NSManagedObject는 연결되어 있는 context 없이는 존재할 수 없습니다.
  • context.save() 를 통해서 저장 해야지 변경사항이 저장됩니다.
// from WWDC19 - Making Apps with Core Data
// (https://developer.apple.com/wwdc19/230)
let rawPostsData: Data = // Server response ... 
if let postDicts: try? JSONSerialization.jsonObject(with: rawPostsData) as? [[String : Any]] {
  context.perform {
		let insertRequest = NSBatchInsertRequest(entity: Post.entity(), objects: postDicts)
		let insertResult = try? context.excute(insertRequest) as! NSBatchInsertRequest
		let success = insertResult.result as! Bool
  }
}
  • 상기한 바와 같이 Core Data의 Object Graph는 메모리에 일단 올라간 이후에 조작이 가능하기 때문에 수 천개 이상의 많은 데이터들이 한번에 입력되거나 변경되게 되면 많은 부하가 발생합니다. 이러한 작업을 해야 하는 경우는 꼭 Batch Insertion이나 Batch Deletion 기능을 활용해서 작업을 해야 합니다. 일반적인 반복문 안에서 하나씩 NSManagedObject를 생성하게 된다면 시간이 너무 오래 걸리게 될 것입니다.

Persistent Container - NSPersistentContainer

NSPersistentContainer 는 iOS10 부터 지원하며, 위에서 다룬 모든 클래스들을 모두 묶어서 관리(오케스트레이션)하는 클래스입니다. 이 클래스를 통해서 각각의 클래스를 초기화 시키는 것보다 Core Data를 훨씬 편하게 사용할 수 있게 해줍니다.

CloudKit(with Core Data)

Intro

Store structured app and user data in iCloud containers that all users of your app can share

설명에도 나와 있는 것처럼 CloudKit은 우리 앱을 사용하는 사용자들이 각종 데이터를 저장할 수 있도록 고안되었습니다. 따라서 Core Data와의 연동 이외에도 단순한 파일의 저장이나, Core Data 연동 없이 iCloud container에서 구조화된 데이터를 가져오는 것도 가능합니다. 하지만 이번에는 Core Data와의 연동에만 집중해서 공부해볼 예정입니다. Core Data 연동 이외의 것이 궁금하시다면 공식문서를 참조해주세요.

Concepts

CloudKit과 Core Data는 데이터를 다루는 방식이 꽤나 유사합니다. 그렇기 때문에 Core Data의 개념이 CloudKit에서 어떻게 사용되는지 정리를 한다면 앞으로 학습에 도움이 될 것입니다.
Image from “Sync a Core Data store with CloudKit public database“ in WWDC20

Image from “Sync a Core Data store with CloudKit public database“ in WWDC20

Use Cases in CloudKit with Core Data

Image from “Sync a Core Data store with CloudKit public database“ in WWDC20

CloudKit을 활용해서 Core Data의 기능을 확장하는 경우는 크게 3가지로 나눌 수 있습니다.

  1. 내가 보유한 기기사이에서 iCloud를 통한 데이터 동기화 - Private
  2. 우리 앱을 사용하는 모든 유저에게 공개되는 데이터 - Public
  3. 앱 내에서 내가 직접 공유한 iCloud 사용자들 사이에서의 데이터 공유 - Shared

이 3가지가 현재 CloudKit에서 제공하는 3가지 형태의 데이터베이스입니다. 이 세가지에 대해서 각각 어떤 개념이 있고 어떻게 구현이 되는지 알아보도록 하겠습니다.

Private Database & Implementation

만약 우리가 Core Data Stack을 사용해서 앱을 만들었다면 대부분의 경우 여러분들의 앱은 이미 CloudKit의 Private Database를 통한 기기간 동기화의 준비를 마친 상태일 것입니다.

상기한 바와 같이 CloudKit과 Core Data는 데이터를 다루는 방식이 유사하기 때문에 매우 간단한 절차를 통해서 기능을 추가할 수 있습니다.

  1. Core Data Stack의 Container를 NSPersistentCloudKitContainer 로 교체하기

    // Core Data만 사용하는 경우 Core Data Stack을 만드는 코드
    container = NSPersistentContainer(name: "CoreData")
    // Class 이름에 CloudKit만 추가해주면 1단계 완료!
    container = NSPersistentCloudKitContainer(name: "CoreData")
  2. 프로젝트의 Capability 추가하기

    1. Background Modes - Remote notifications 추가
    2. iCloud - CloudKit 추가
    3. Push Notifications 추가
  3. 끝! 이제 서로 다른 기기에서 앱을 실행하면 서로 동기화가 시작됩니다.

이렇게 엄청 간단하기 모든 애플 기기와의 동기화 작업이 끝났습니다! 바로 다음으로 넘어가보죠

Public Database

여기서부터는 조금 까다로운 상황들이 발생합니다. Public Database 의 대표적인 Use case들은 다음과 같습니다.

  • 앱을 사용하는 모든 사람들을 위한 데이터(게임의 스코어보드, 공지사항 등)
  • 개발자가 생산한 데이터(앱의 사용성을 높이기 위한 템플릿이나 프리셋 등)
  • 유저들이 공개를 목적으로 생성한 데이터

이러한 데이터들은 필요에 따라 앱의 모든 사용자들이 로그인 여부와 상관없이 보여지고 싶은 경우도 있고, 권한에 대한 설정들도 조금 복잡해집니다.

그래서 CloudKit에서 Database를 다루는 방식이 .private 과 .public 이 서로 다릅니다.
Image from “Sync a Core Data store with CloudKit public database“ in WWDC20

그리고 이 차이는 데이터를 읽고 쓰는 권한의 영역에서는 더 복잡해집니다
[Image from “Sync a Core Data store with CloudKit public database“ in WWDC20](https://developer.apple.com/wwdc20/10650)

위의 이미지는 Private Database에서의 로그인 여부에 따른 권한에 대한 도표입니다. 굉장히 심플하죠?

반면 Public Database의 경우는 훨씬 복잡합니다. 특히 Modify 부분의 수정 및 삭제는 Public이라는 특성 때문에 단순하게 접근하기 매우 힘듭니다. 특히 하나의 Entity가 Public 과 Private 모두에서 관리되는 중이라면 더 복잡해지게 됩니다.

한가지 예시로, 이 화면에서는 Post들의 목록이 표시됩니다. 그리고 이 Post들은 Private / Public 모두에서 관리하고 있어서 저 화면만으로는 어떤 Post가 어떤 데이터베이스에서 관리되고 있는지 알 수 없기 때문에 Public Database에서 관리되고 있는 레코드를 Edit 버튼으로 수정하려고 할 때 문제가 발생할 수 있습니다.

이렇듯 Public Database를 사용할 때는 그 구조적인 차이로 인해서 Private Database를 사용하는 것에 비해서 여러가지 제약사항들을 가지게 됩니다.

  1. Public Database에서 관리되는 Entity들은 Private 에서 관리되는 Entity들과 Relation을 설정할 수 없습니다.
  2. Private Database에서는 어느 한 기기에서 오브젝트가 지워지면 모든 기기에서 변경사항이 적용되기 전까지 해당 사항을 tombstone 이라고 불리우는 곳에 남겨두고 삭제된 사실을 다른 기기에 전파합니다. 하지만 Public Database에서는 오브젝트가 지워지게 되면 따로 tombstone에 기록하지 않고 지워버립니다. 그래서 다른 기기들은 오브젝트가 지워진 사실을 전파받지 못하고 가지고 있게 됩니다.

특히 1번의 제약사항 때문에 CloudKit 도입의 가능성이 있다면 CoreData를 설계할 단계에서부터 오브젝트를 잘 정의해야 합니다.

Public Databse - Implementation

이제 실제 구현을 해보도록 하겠습니다. 가상의 앱을 통해서 한번 실습을 해보죠.

💡 SmallTalk 앱은 시덥지 않은 일들을 기록하는 어플입니다. 이 시덥지 않은 기록들은 iCloud를 통해 내가 소유한 모든 기기에서 굳이 동기화가 됩니다. 이제 우리는 새로운 탭에 공지사항 기능을 추가하려고 합니다. 놀랍게도 이 어플의 공지사항은 iCloud 계정을 가진 모두가 작성할 수 있습니다

아래 작업은 이 리포지토리의 cloudkit-public-start 브랜치 로 직접 실습해보실 수 있습니다.
- 등록비를 지불한 개발자 계정에서만 동작 확인이 가능합니다
- 예시는 예시일 뿐 모든 상황에 동일하게 동작하지 않습니다. 단계별로 따라하기 보다는 각각의 단계가 어떤 작업을 하는지 아는 것이 훨씬 더 중요합니다.

  1. 우선 Public Database에서 관리할 새로운 오브젝트를 만들어야 합니다. - Notice **Entity를 추가했습니다.

  2. CloudKit은 기본 설정으로 모든 오브젝트들이 하나의 Database에서 관리되도록 하고 있습니다. 지금 시나리오는 새로운 오브젝트를 만들고 그 오브젝트는 Public으로만 관리가 되어야 하기 때문에 새로운 Configuration을 만들어줘야 합니다. 같은 화면 CONFIGURATIONS Section에서 작업을 합니다.

  • 상세 단계들
    1. 이제 Default는 CloudKit을 통해 오브젝트를 관리하지 않습니다. 우측 윈도우에서 Used with CloudKit의 체크를 해제 해주세요
    2. 모든 Entity들을 각자 역할에 맞게 Private, Public Configuration으로 옮깁니다. 두 설정에서는 Used with CloudKit도 활성화 시켜주세요
    3. 위에서 만든 Configuration을 사용해서 실제로 적당한 Store를 만들어야 합니다. Core Data Stack에서는 Container에게 설정할 정보를 담은 description을 알려주면 Store를 만들고 관리해줍니다. PersistentController 의 초기화 함수를 수정합니다
  1. 이제 CloudKit console 작업만 남았습니다. 먼저 Notice를 CloudKit에 저장하기 위해 새로운 Record Type을 만들어줍니다. Chat을 작성해서 동기화 해봤다면 이를 참고해서 만들 수 있습니다. 새로운 타입은 Record Types 메뉴에서 만들 수 있습니다.
  2. 같은 방법으로 Indexes에서 Index도 만들어주세요. 특히 Public Database 에서 사용하려는 모든 Entity 들은 recordName, modifiedTimeStamp 이 두가지에 대해서 Queryable을 추가하고, modifiedTimeStamp에는 Sortable을 추가한 index를 생성해야 합니다.
  3. 이제 Public Database를 사용하기 위한 준비를 마쳤습니다. UI를 만들고 테스트 해보시면 됩니다.

References

Sample Codes

Apple

Documents from Apple

Apple Developer Documentation

Videos from Apple

Making Apps with Core Data - WWDC19 - Videos - Apple Developer

What's new in CloudKit - WWDC21 - Videos - Apple Developer

Sync a Core Data store with the CloudKit public database - WWDC20 - Videos - Apple Developer

Build apps that share data through CloudKit and Core Data - WWDC21 - Videos - Apple Developer

Etc

Core Data Overview

Welcome to Core Data Fundamentals

https://github.com/DeveloperAcademy-POSTECH/PouringDiary

profile
이것 저것 즐겨하는 개발자입니다.

0개의 댓글