Core Data with CloudKit(三)——CloudKit儀表臺

語言: CN / TW / HK

本篇文章中,我們將一起研究CloudKit儀表臺。

初識儀表臺

使用CloudKit Dashboard需要開發者擁有Apple Developer Program賬號,訪問https://icloud.developer.apple.com即可使用。

image-20210808161150623

最近兩年蘋果對CloudKit儀表臺的佈局做過較大的調整,上面的截圖是2021年中時的樣子。

儀表臺主要分為三個部分:

  • 資料庫(CloudKit Database

資料庫Web客戶端。涵蓋管理SchemaRecordZone、使用者許可權、容器環境等功能。

  • 遙測(Telemetry

使用直觀的視覺化效果,深入瞭解應用程式的伺服器端效能以及跨資料庫和推送事件的利用率。

  • 日誌(Logs

CloudKit 伺服器生成實時和歷史日誌,記錄並顯示應用程式和伺服器之間的互動。

在絕大多數使用Core Data with CloudKit的場景下,我們僅需要使用儀表板中極少數的功能(環境部署),但利用CloudKit Dashboard,我們可以更清楚的瞭解Core Data資料同步背後運作的一些機制。

資料庫(CloudKit Database)

image-20210808163319683

Core Data with CloudKit (一) —— 基礎中已經對CKContainerCKDababaseCKZoneCKSubscriptionCKRecord等基礎物件做了簡單的說明,本文還將介紹CloudKit的其他一些物件和功能。

環境

CloudKit為你的應用程式網路資料分別提供了開發環境(Develpment)和生產環境(Production)。

  • 開發環境

當你的專案仍處於開發階段時,所有通過CloudKit產生的資料都只被儲存開發環境中,只有開發團隊的成員才能訪問該環境中的資料。在開發環境中,你可以隨時進行Schema結構調整、對Record Type的屬性進行刪除修改等操作。即使這些操作可能會引起不同版本之間資料衝突都沒有問題(可以隨時重置開發環境)。非常類似Core Data的應用程式上線前的狀態,即使資料無法正常遷移,只需要刪除重灌app即可。通過開發環境,開發者可以在向用戶提供CloudKit服務之前對應用程式進行充分的測試。

  • 生產環境

當應用程式完成開發並準備提交應用商店時,需要將開發環境的結構部署到生產環境(Deploy Schema Changes)。Schema一旦部署到生產環境,則意味著開發者不可以像在開發環境中那樣隨意對Schema進行修改,所有的修改都必須以向前相容的方式進行。

原因非常簡單,一旦應用程式上線,我們無法控制客戶端的更新頻率,也就是客戶端可能存在任何的結構版本,為了能夠讓低版本的客戶端一樣可以訪問資料,任何對資料模型的更改都需要向下相容。

App Store上銷售的應用程式只能訪問生產環境。

即使開發者的開發者賬戶同個人iCloud賬戶一致,開發環境和生產環境也是兩個不同的沙盒,資料是互不影響的。當使用Xcode除錯程式時,應用只能訪問開發環境,而通過TestflightApp Store下載的應用則只能訪問生產環境。

在開發環境下,點選Deploy Schema Changes將開發環境的Schema部署到生產環境。

image-20210808180259192

部署時,會顯示自上次部署後開發環境做出的修改。

即使Schema已經部署到生產環境後,我們仍可繼續改動開發環境並部署到生產環境,如果模型無法滿足相容條件,CloudKit儀表臺將會禁止你的部署行為。

image-20210808175543219

在容器名稱下方會顯示Schema是否已經部署到生產環境。上圖是尚未部署的狀態,下圖是已經部署的狀態。

image-20210808180421055

image-20210808180014216

在做任何操作之前,要首先確認是否處於正確的環境設定中。

鑑於CloudKit的環境部署規則,在採用Core Data with CloudKit的專案中設計Core Data資料模型時一定要特別小心!。我個人的原則是可加、不減、不改。我將在下篇文章詳細討論該如何對Core Data with CloudKit資料模型做版本遷移。

安全形色(Security Roles)

安全形色僅適用於公共資料庫。

CloudKit使用基於角色的訪問控制(RBAC)來管理許可權和控制對公共資料庫中資料的訪問(私有資料庫對於應用程式的使用者是唯一的)。通過CloudKit,你可以為一個角色設定許可權級別,然後將該角色分配給一個給定的記錄型別(Record Type)。

許可權包括讀、寫、建立。讀許可權只允許讀取記錄,寫許可權允許讀取和寫入記錄,而建立許可權允許讀取和寫入記錄以及建立新紀錄。

CloudKit包含3個預設角色,分別為World(_world)、Authenticated(_icloud)和 Creator(_creator)。World表示任何人,無論其是否為iCloud使用者。Authenticated適用於任何經過驗證的iCloud使用者。Creator則是作為記錄(Record)的建立者。

image-20210808210401070

預設的設定為,任何人都可以讀取資料,只有經過驗證的iCloud使用者才可以建立新紀錄,記錄的建立者可以更新自己的記錄。

image-20210809062640040

我們可以建立自定義安全形色,但是不能建立使用者記錄(User Record),當用戶第一次對容器進行身份驗證時時系統會為該使用者建立使用者記錄。我們可以查詢現有使用者並將其分配給任意的自定義的角色。

安全形色是資料模型(Schema)的一部分,每當開發者修改了安全設定後,需要將其部署到生產環境才能在生產環境生效。部署後無法刪除安全形色。

大多數Core Data with CloudKit應用場合,直接使用系統的預設配置即可。

索引(Indexes)

CloudKit的索引分為三種類型:

  • 可查詢(queryable
  • 可搜尋(searchable
  • 可排序(sortable

當我們通過CloudKit建立Recored Type後,可以根據需要為每個欄位建立所需的索引(只有NSString支援可搜尋)。索引型別選項是獨立的,如果你希望該欄位既可查詢又可排序,則需要分別建立兩個索引。

image-20210809064449042

只有為Record TyperecordName建立了queryable索引後,才可以在Records中瀏覽該Type的資料。

image-20210809065509228

image-20210809064743215

Core Data with CloudKit會自動為Core Data資料模型的每個屬性在CloudKit上建立需要的索引(不包含recordName)。除非你需要在CloudKit儀表臺上瀏覽資料,否則我們不需要對索引做任何新增。

Record Types

Record Type是開發人員為CKRecord指定的型別識別符號。你可以直接在程式碼中建立它,也可以在CloudKit儀表盤上對其進行建立、修改。

image-20210809073043092

基礎篇中曾提到Entity相較Record Type擁有更多的配置資訊,但Record Type也有一個Enitity沒有的特性——元資料。

image-20210809075124786

CloudKit為每一個Record Type預設了若干元資料欄位(即使開發者沒有建立任何其他欄位),每條資料記錄(CKRecord)都會包含這些資訊,其中絕大多數都是系統自動設定的。

  • createdTimestamp

CloudKit首次將記錄儲存到伺服器的時間

  • createUserRecordName

_creator的使用者記錄,該記錄儲存在Users(系統建立)中,每當使用者第一次對容器進行身份驗證時時系統會為該使用者建立使用者記錄

  • _etag

版本令牌。每次CloudKit儲存記錄時,都會將該記錄更新為新值。用於比較網路和本地資料的版本

  • modifiedTimestamp

CloudKit更新記錄的最近時間

  • modifiedUserRecordName

最後更新資料的使用者記錄

  • recordName

記錄的唯一 ID。在建立CKRcord時建立,通常會設定為UUID字串

對於一些特殊型別的Record Type,系統還會增加一些針對性的元資料,比如role,cloud.shared

本文的主題為Core Data with CloudKit,因此讓我們來看一下NSPersistentCloudKitContainer是如何將Core Data託管物件的屬性轉換成CloudKitRecore Type欄位的。

image-20210809104558352

image-20210809104402659

上圖是我們在同步本地資料庫到iCloud私有資料庫中模版專案ItemCloudKit對應的Record TypeCloudKit會自動為託管物件實體的每個屬性創欄位,將屬性名稱對映到了具有CD_[attribute.name]鍵名的欄位。該欄位的型別在Core DataCloudKit之間可能也會有所不同。Record Type名稱為CD_[entity]。一切的操作都是由系統自動完成的,我們無需干預。另外,還會為Enitity生成一個CD_entityName的欄位,內容為Entity的類對映名。

這些以CD_為字首的字串,在資料同步過程中將不斷出現在控制檯上,瞭解了它的構成對除錯程式碼有一定幫助。

Record Type部署到生產環境後,欄位不可以刪除,欄位名稱也不可以修改。因此一些Core Data中的操作在Core Data with CloudKit中是不允許的。

不要對已經上線的應用程式資料模型的Entity進行更名,也不要對Attribute更名,即使使用Mapping Model、Renaming ID都是不行的。在開發階段如果需要更名的話,可能需要刪除app重灌並重置CloudKit的開發環境。

Zones

每個種類的資料庫都有預設Zone,只有私有資料庫可以自定義Zone

image-20210809143010363

對於私有資料庫中的資料,在建立CKRecord時可以為資料指定Zone

```swift let zone = CKRecordZone(zoneName: "myZone") let newStudent = CKRecord(recordType: "Student", recordID: CKRecord.ID(recordName: UUID().uuidString, zoneID: zone.zoneID))

```

NSPersistentCloudKitContainer在將託管物件轉換成CKRecord時,將ZoneID統一設定為com.apple.coredata.cloudkit.zone。必須切換到正確的Zone才能瀏覽到資料。

image-20210809143648531

  • OWNER RECORD NAME

使用者記錄,對應Zone_creator

  • CHANGE TOKEN

比對令牌

  • ATOMIC

當CloudKit無法更新Zone中的一個或多個記錄時,如果值為true則整個操作失敗

Records

用於資料記錄的瀏覽、建立、刪除、更改、查詢。

image-20210809150327144

在瀏覽資料時,需注意以下幾點:

  • 選擇正確的環境(開發環境和生產環境的資料完全不同)
  • 選擇正確的DatabaseZone
  • 確認需要瀏覽的Record Type元資料recordName已經添加了queryable索引
  • 如果需要對欄位進行排序或過濾,請給該欄位建立對應的索引
  • 索引只有在部署後才會在生產環境下起作用

CloudKit儀表臺中修改Core Data的映象資料,客戶端會立即收到遠端通知並進行更新。不過並不推薦此種做法。

你也可以在程式碼中獲取到Core Data託管物件對應的CKRecord

swift func getLastUserID(_ object:Item?) -> CKRecord.ID? { guard let item = object else {return nil} guard let ckRecord = PersistenceController.shared.container.record(for: item.objectID) else {return nil} guard let userID = ckRecord.lastModifiedUserRecordID else { print("can't get userID") return nil } return userID }

上面的程式碼,將獲取託管物件記錄對應的CKRecord的最後修改使用者

Subscriptions

瀏覽在容器上註冊的CKSubscription

CKSubscription是通過程式碼建立的,在儀表盤上只可以檢視或刪除。

比如下面的程式碼將建立一個CKQuerySubscription

```swift let predicate = NSPredicate(format: "name = 'bob'") let subscription = CKQuerySubscription(recordType: "Student", predicate: predicate, options: [.firesOnRecordCreation]) let info = CKSubscription.NotificationInfo() info.alertLocalizationKey = "create a new bob" info.soundName = "NewAlert.aiff" info.shouldBadge = true info.alertBody = "hello world"

    subscription.notificationInfo = info

    publicDB.save(subscription) { subscription, error in
        if let error = error {
            print("error:\(error)")
        }
        guard let subscription = subscription else { return }
        print("save subscription successes:\(subscription)")
    }

```

image-20210809154503445

NSPersistentCloudKitContainer會為Core Data映象的私有資料庫註冊一個CKDatabaseSubscription。當com.apple.coredata.cloudkit.zone資料更新時,會推送遠端通知。

image-20210809154946576

Tokens&Keys

設定容器的API令牌。

image-20210809152554058

除了可以通過程式碼和CloudKit儀表臺對資料進行操作外,蘋果還提供了從網路或其他平臺訪問iCloud資料的手段。在獲取令牌後,開發者還可以通過使用 CloudKit JS CloudKit Web 服務與資料進行互動。

已有開發者利用以上服務,開發出可在其他平臺訪問iCloud資料的第三方庫,比如DroidNubeKit(在安卓上訪問CloudKit)。

對於Core Data的網路映象資料,除非你的資料模型足夠簡單,否則不推薦做這種嘗試。CloudKit Web服務更適合直接通過Cloudkit建立的資料記錄。

Sharing Fallbackd

為低版本作業系統(低於iOS 10、macOS Sierra)提供資料記錄共享回撥支援。

遙測(Telemetry)

image-20210809161022705

通過檢視Telemetry的指標,方便你在開發或更新應用程式時視覺化效能。包括請求數量、錯誤數量、推送數量、伺服器延遲以及平均請求大小等等。通過設定範圍,僅顯示與你相關的資料,幫助你更好地瞭解應用程式的流量配置及使用趨勢。

日誌(Logs)

image-20210809162346212

在歷史日誌中,你可以檢視包括時間、客戶端平臺版本、使用者(匿名)、事件、組織、細節等資訊。

在提供詳盡資訊的基礎上,CloudKit儘可能地保持使用者資料的隱祕性。日誌顯示每個使用者記錄的伺服器事件,但不暴露任何個人身份資訊。僅顯示匿名的、特定於容器的CloudKit使用者。

AppStoreConnect的分析資訊僅來自已同意與 App 開發者共享診斷和使用資訊的使用者,CloudKit日誌資訊則來自於你的應用程式中所有使用了CloudKit服務的使用者。兩者結合使用,可以獲得更好的效果。

總結

大多數使用Core Data with CloudKit的場景,開發者基本無需使用CloudKit儀表盤。不過偶爾研究一下儀表盤上的資料,也是一種不錯的樂趣。

下一篇文章,我們將聊一下開發Core Data with CloudKit專案經常會碰到的一些情況,比如除錯、測試、資料遷移等。

本文原載於我的個人部落格肘子的Swift記事本

文章也一併釋出在微信公共號:肘子的Swift記事本

其他推薦:

Core Data with CloudKit 1-6

如何在Xcode下預覽含有Core Data元素的SwiftUI檢視

https://www.fatbobman.com/posts/uikitInSwiftUI/

用NavigationViewKit增強SwiftUI的導航檢視

@AppStorage研究