• 여러 기기간에 데이터 공유를 위해 클라우드를 사용한다.
  • Core Data와 CloudKit의 비교
    • Core Data는 로컬 영속 저장소를 제공한다.
    • CloudKit은 분산 영속 저장소를 제공한다.
    • 둘 다 애플 모든 플랫폼에서 동작한다.
    • 용어 비교
      • Objects - NSManagedModel ↔ CKRecord
      • Models - NSManagedObjectModel ↔ Schema
      • Stores - NSPersistentStore ↔ CKRecordZone / CKDataBase
  • 이 둘은 비슷하기 때문에, 함께 쓸 수 있다면 좋을 것이다.
    • XCode 11부터 프로젝트를 만들때 Core Data를 쓴다면 CloudKit 사용 옵션에도 체크할 수 있다.
    • iCloud Capability는 따로 켜줘야 한다.
  • iOS 13부터, NSPersistentCloudKitContainer라는 것이 추가 되었다.
    • NSPersistentContainer의 서브클래스로, 이 한 줄만 바꾸면 CloudKit을 사용하는 것이 된다.
    • 이는 일반적으로 CloudKit을 통해서 싱크를 맞추는 패턴을 미리 구현해놓아서 수천줄의 코드를 아끼기 위해서 만들어졌다.
  • NSPersistentCloudKitContainer의 특징
    • CloudKit에 있는 데이터를 로컬에 복사해둔다 → 접근 속도를 높이기 위함
    • 강력한 스케쥴링과 에러 복구 기능을 제공해서 신경을 덜 써도 된다. → 로컬 복사본과 원격 저장소간의 싱크를 위해서 반드시 필요한 부분이다.
    • NSManagedObject와 CKRecord간의 전환 기능을 제공한다.
  • NSPersistentCloudKitContainer의 확장
    • 여러개의 store 사용

      • 필요성

        • 여러 유즈 케이스에 따라서 데이터 분리
        • 서로 다른 방식으로 제약 검사 가능
        • 데이터가 굉장히 자주 바뀌는 경우, 이를 몰아서 싱크를 맞춰줄 필요가 있다.
      • 사용방법

        • Configuration을 만들어서 managing하는 데이터를 조정해준다.
        let container = NSPersistentCloudKitContainer(name: "CloudKitContainer")
        
        let local = NSPersistentStoreDescription(url: URL(fileURLWithPath: "/files/local.sqlite"))
        local.configuration = "Local"
        
        let cloud = NSPersistentStoreDescription(url: URL(fileURLWithPath: "/files/cloud.sqlite"))
        cloud.configuration = "Cloud"
        cloud.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.wwdc.memo")
        
        let shared = NSPersistentStoreDescription(url: URL(fileURLWithPath: "/files/shared.sqlite"))
        shared.configuration = "Shared"
        shared.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.com.wwdc.shared")
        
        container.persistentStoreDescriptions = [ local, cloud, shared ]
        
        
    • CloudKit Schema와의 사용

      • record type과 entity name

        @objc(Post)
        public class Post: NSManagedObject {
        	@NSManaged public var title: String?
        	@NSManaged public var content: String?
        	@NSManaged public var attachment: NSSet?
        	@NSManaged public var tags: NSSet?
        }
        
        // CD_: Core Data가 CloudKit이 관리하는 영역과의 분리를 위해 붙이는 접두사
        <CKRecord: 0x7fd0b2539220; recordID=Post_UUID, values={
        	"CD_content" = "An example core data string";
        	"CD_content_ckAsset" = "<CKAsset: 0x7fd0b2515c20;...>";
        	"CD_title" = "An example core data string";
        	"CD_title_ckAsset" = "<CKAsset: 0x7fd0b240bd40;...>";
        	"CD_entityName" = Post; // 실제 entity와의 연결 다리. 이를 통해서 entity inheritance 구현이 가능하다.
        }, recordType=CD_Post // 
        
      • Asset Externalization

        • 데이터를 content와 ckAsset으로 분리해서 저장
        • 임의의 크기의 데이터를 저장할 수 있게 만들어준다.
        • 항상 존재하는 것은 아니고 데이터가 CloudKit의 제한인 1MB를 넘어가면 생긴다. → 존재 여부를 체크해야 한다.
      • Relationships

        • many-to-one

          • CloudKit은 CKReference로 표현하지만, Core Data와 다르게 갯수 제약이 있기 때문에 relationship을 따로 저장하는 방식을 쓴다.
          @objc(Attachment)
          public class Attachment: NSManagedObject {
          	@NSManaged public var post: Post?
          }
          
          <CKRecord: 0x7fd0b4011bf0; recordID=Attachment_UUID, values={
          	"CD_entityName" = Attachment;
          	"CD_imageData" = "ImageData_UUID";
          	"CD_post" = "Post_UUID";
          	"CD_uuid" = "...";
          }, recordType=CD_Attachment>
          
        • many-to-many

          @objc(Tag)
          public class Tag: NSManagedObject {
          	@NSManged public var posts: NSSet?
          }
          
          <CKRecord: 0x7fd0b2419da0; recordID=Tag_UUID, values={
          	"CD_color" = {length = 17, bytes = 0x...};
          	"CD_color_ckAsset" = "<CKAsset: 0x7fd0b2532de0; ...>";
          	"CD_name" = "An example Core Data string";
          	"CD_name_ckAsset" = "<CKAsset: 0x7fd0b24215d0;...>";
          	"CD_uuid" = "..."
          	"CD_entityName" = Tag;
          }, recordType=CD_Tag>
          
          // CDMR: Core Data Mirrored Relationship
          // Join된 테이블을 만들어 놓는 것과 같다.
          <CKRecord: 0x7fd0b24202f0; recordID=CDMR_UUID, values={
          	"CD_entityNames" = "Post:Tag";
          	"CD_recordNames" = "Post_UUID:Tag_UUID";
          	"CD_relationships" = "tags:posts"
          }, recordType=CDMR>
          
    • 협업을 위한 데이터 모델링

      • 충돌 문제는 NSPersistentCloudKitContainer가 알아서 해준다.(last writer wins merge policy로)
      • 다만 이게 완전하지 않은 경우도 있다. 여러 디바이스에서 동시에 같은 데이터를 수정할 경우, 한쪽의 수정사항이 완전히 사라질 수 있다.
        • 그냥 이어 붙일수도 있지만, 결과물이 영 좋지 않을 수도 있다.
      • 해결법
        • flat한 데이터(ex. String)대신 relationship으로 별도로 빼기 → 통짜 String을 수정하는 게 아니라 Contribution형태로 수정사항을 쌓아가는 형태
          • 이때 수정 순서가 중요하므로 Ordered로 한다. 시간 순서로 한다던지...
          • 다만 시간은 분산 시스템에서는 문제가 많다. 그래서 causal tree형태로 conflict-free 데이터 구조를 만든다.