CloudKit、またはZenスタイルの同期で作業を簡素化

はじめに



クラウド同期は、ここ数年の自然な傾向です。 1つまたは複数のAppleプラットフォーム(iOS、macOS、tvOS、watchOS)向けに開発しており、タスクがアプリケーション間で同期機能を実装することである場合、非常に便利なツール、またはサービス全体(CloudKit)を自由に使用できます。



私たちのチームは、データウェアハウスとしてCoreDataを使用するプロジェクトを含め、CloudKitを使用してデータ同期機能を常に強化する必要があります。 そのため、同期用のユニバーサルインターフェイスを作成するというアイデアが生まれ、実現しました。



CloudKitは単なるフレームワークではありません。 これは完全なBaaS(サービスとしてのバックエンド)、つまり クラウドストレージ、プッシュ通知、アクセスポリシーなどを含む完全なインフラストラクチャを備えた包括的なサービス、およびユニバーサルクロスプラットフォームプログラミングインターフェイス(API)を提供します。







CloudKitは使いやすく、比較的手頃な価格です。 あなたがApple Developer Programのメンバーであるという理由だけで、あなたは完全に自由に使用できます:





そして、そのような必要性がある場合、これらの数を増やすことができます。 CloudKitはユーザーのiCloudストレージを使用しないことに注意してください。 後者は認証にのみ使用されます。



この記事は、CloudKitの広告ではなく、CloudKitの基本的な動作の概要でもありません。 プロジェクトの設定、開発者プロファイルでのアプリIDの設定、CloudKitダッシュボードでのCKコンテナまたはレコードタイプの作成については何もありません。 さらに、バックエンドコンポーネントだけでなく、CloudKit APIに直接関連するソフトウェアコンポーネント全体もこの記事の範囲外です。 CloudKitを使用した作業の基本を正確に理解したい場合は、これに関する優れた入門記事が既にありますが、繰り返しの意味はありません。





この記事は、ある意味、次のステップです。



長い間使用しているものを既に習得している場合、遅かれ早かれ疑問が生じます。プロセスを自動化する方法、それをさらに便利で統一する方法です。 これが、デザインパターンの由来です。 これにより、CloudKitとの連携を促進するためのフレームワークであるZenCloudKitが作成されました。このフレームワークは、すでに多くのプロジェクトで正常に適用されています。 それについて、つまり、CloudKitを使用する新しい技術的な方法について、さらに説明します。



ユニバーサルインターフェース



フレームワークの作成の最終目標は、CoreDataエンティティと互換性のあるインターフェイスを実装することで、既存のアーキテクチャの複雑さに関係なく、最小限の労力で、既存のデータベース接続を考慮して、同期(データの保存、削除、受信)を可能にします。



このフレームワークはSwift 3で記述されており、Swiftの開発者は、その使用がもたらす利点を十分に理解することができます。 Objective-Cの場合、完全なブリッジを使用できますが、よく知られている理由により、同様のものは冗長になり、実装が面倒になります。 この記事のコード例は、Swiftで作成されます。



並行して、実装の例を検討します。



実装例



いくつかの典型的な同期操作を紹介として見てみましょう:メソッドの保存と削除。 最終的な実装は次のとおりです。











ここで何が起こっていますか?



エンティティがNSManagedObjectであるエンティティプロパティを持つイベントオブジェクトがあるとします。 このNSManagedObjectは、他のデータベースオブジェクトと同様に、他のNSManagedObjectオブジェクトへのプロパティ、リンク、 参照などのフィールドがあり、1対1または1対多の関係を形成します。



このオブジェクトを同期または非同期でCloudKitデータベースに保存(または対応するオブジェクトを削除)するために、すべての接続を転送するために、適切なメソッドを含むプロキシオブジェクト-iCloudが使用されます。 entity.iCloud.save()(非同期)またはentity.iCloud.saveAndWait()(同期保存)を呼び出すだけで、エンティティのすべてのフィールドがCloudKitオブジェクトの対応するフィールドに書き込まれ、新しく保存されたCKRecordから一意のUUID(つまり文字列) CKRecordIDオブジェクトのrecordNameプロパティ)は、この目的のために指定されたエンティティオブジェクトのフィールドに自動的に書き戻され、それによりローカルオブジェクトとリモートオブジェクト間の接続が形成されます。



CloudKitを使用したことがなく、すべてが奇妙に聞こえる場合、エンティティに.iCloud.save()があると言う方が簡単です。これは、オブジェクト自体とそのすべての接続の両方を保存するのに十分です。 異なるエンティティやクライアントコードの汚れに対応する同一のメソッドはもうありません。 便利ですね。



同期オブジェクトを構成する



これが機能するためには、いくつかの条件を満たす必要があります。



この作業は、多くのライブラリ、さまざまなWebパーサー(RestKitなど)などで広く使用されているプロパティマッピングスキームに基づいています。 マッピングは、NSObjectの相続人によってのみサポートされるKVCを介して、古典的な方法で実装されます。 したがって、最初の条件:



1)各同期オブジェクトはNSObjectの子孫である必要があります(たとえば、NSManagedObjectが最適です)。



2)各同期オブジェクトは、次のようなZKEntityプロトコルを実装する必要があります。











CoreDataを使用する場合は、(サブ)クラスに直接実装する必要があります。











プロトコルからわかるように、必須フィールドはrecordTypeとmappingDictionaryです。 両方を検討してください。



//必須(必須フィールド)



1) recordType - CloudKitの対応するレコードタイプRecord Type。



例:Personクラスには、プロパティrecordType =“ Person”が含まれます。 インスタンスでsave()を呼び出した後、このテーブルのCloudKitダッシュボード(「Person」)でレコードが開きます。



実装:



static var recordType = "Person"
      
      





2) mappingDictionary-マッピングプロパティの辞書。

スキーム:[ローカルキー:リモートキー(CloudKitテーブルのフィールド)]。



例:Personクラスには、firstNameフィールドとlastNameフィールドが含まれています。 それらを同じ名前でCloudKitのPersonテーブルに保存するには、次のように記述する必要があります。



 static var mappingDictionary = [ "firstName" : “firstName”, “lastName” : “lastName” ]
      
      







//オプション(オプションのフィールド)



残りのプロトコルフィールドはオプションです。



3) syncIdKey-リモートオブジェクトのIDを格納するローカルプロパティの名前。 IDは、通信ローカル<—>リモートに必要なオブジェクトパスポートです。



このフィールドは条件付きでオプションです。 以下で説明するFramework Controllerを初期化するときに、すべてのエンティティのプロパティの名前を指定できます。 ただし、エンティティクラスで個別に指定され、優先度が高く、解析時に最初にチェックされます。 そして、実装が空の場合にのみ、ユニバーサルキーが使用されます(以下を参照)。



実装:



 static var syncId: String = "cloudID"
      
      





changeDateKey-オブジェクトが変更された日付を保存するローカルプロパティの名前。 同期に必要な別のユーティリティ機能。



前のものと同様に、条件付きでオプションです。 実装を省略して、ZenCloudKitの初期化中にすべての同期オブジェクトのプロパティ名を指定することができます(以下を参照)。



実装:



 static var changeDateKey: String = "changeDate"
      
      





リファレンス -1対1の通信を実装するキーを含む辞書。

回路図:[「ローカルキー」:「リモートキー」]



ここでの要件は、「ローカルキー」プロパティが基本要件を満たすクラスのタイプを持つことです(NSObjectを継承し、ZKEntityプロトコルを実装します)。



ローカルオブジェクトでsave()が呼び出されると、ZenCloudKitはそれに関連するすべてを保存しようとします。



実装:



 static var references : [String : String] = ["homeAddress" : "address"]
      
      





referenceLists -ZKRefListオブジェクトの配列を含む辞書。各オブジェクトは特定の接続に関する情報を保持します* -to-many:オブジェクトのタイプと、このリストを要求および保存するキーの名前。



スキーマ:ZKRefList(entityType:ZKEntity.Type、

localSource:ZKEntityオブジェクトの配列を返すローカルプロパティ、

remoteKey:リンクの配列を格納するCloudKitのキー(CKReference))



実装:



  static var referenceLists: [ZKRefList] = [ZKRefList(entityType: Course.self, localSource: "courseReferences", remoteKey: "courses")]
      
      







courseReferencesは、ユーザーが保存するZKEntityオブジェクトの配列と、ルートオブジェクトのリンクのリストに配置するリンクを返すユーザー定義のプロパティです。



コード(続き):



  var courseReferences : [Course]? { get { return self.courses?.allObjects as? [Course] } set { DispatchQueue.main.async { self.mutableSetValue(forKey: "courses").removeAllObjects() self.mutableSetValue(forKey: "courses").addObjects(from: newValue!) } }
      
      





アプリケーションがCloudKitから受け取ったオブジェクトを保存できるように、適切なセッターを実装することも必要です。 したがって、ZKRefListオブジェクトのlocalSourceフィールドは、基本的に入力および出力操作を処理するハンドラーへの参照です。



isWeakはオプションのフラグで、設定されている場合(true)、このタイプのインスタンスを参照する他のオブジェクトがCloudKitで弱いリンク(弱い修飾子を持つアナロジー)を形成することを示します。 これは、それへのリンクを含むオブジェクトが削除されるとすぐに、それに関するレコードがカスケードされることを意味します。



例:オブジェクトBを参照するオブジェクトAがあります



B.isWeak = trueに設定した場合、オブジェクトAはBへの「弱いリンク」でCloudKitに保存されます。オブジェクトBは、オブジェクトAを削除するとすぐに自動的に削除されます。



このフラグはCloudKitネイティブAPIの実装であり、.deleteSelfフラグを使用してCKReferenceコンストラクターにアピールします。



 CKReference.init(record: <CKRecord>, action: .deleteSelf)
      
      





したがって、削除の仕組みは完全にCloudKitの特権であり、フレームワークはより便利なインターフェイスを提供するだけです。 将来、この機能を拡張して、異なるエンティティに対してカスケード削除を構成できるようにすることができます。



実装:



 static var isWeak = true
      
      





referencePlaceholder-宣言された場合、CloudKitからオブジェクトを取得するときにnil値を回避し、デフォルト値で置き換えるプロパティ。



CoreDataエンティティオブジェクトに別のオブジェクトへの参照として常にnil以外の値が含まれていると想定される場合、同期中にこのオブジェクトがCloudKitに存在しない場合は常に、ローカルプロパティをデフォルト値に自動的に設定できます。



例:プロパティbを持つクラスAと、それをミラーリングするCloudKitの同じレコードタイプがあります。



CloudKitにはオブジェクトAがあり、これはローカルに欠落しており、Bへの空の参照(値なし)を持っています。 通常のシナリオでは、同期の結果として、プロパティbがnilになるオブジェクトAを取得します。 ただし、ローカルクラスで設定されたデフォルト値( referencePlaceholder = ...)では、ZenCloudKitは指定した値をプロパティbに自動的に割り当てます。



Ab = referencePlaceholder、



後者はBのインスタンスです。



そのため、完全な同期サイクルの結果、他のすべてのデバイスで空のままにされていても、リンクが塗りつぶされたオブジェクトは常にアプリケーション内に作成されます。



実装:



 static var referencePlaceholder: ZKEntity = B.defaultInstance()
      
      





referencePlaceholderはターゲットクラスで指定されることに注意してください。 オブジェクトAのプロパティbがnil(Ab!= Nil)ではないことが必要な場合、同期の結果として取得したルートクラスAではなく、referencePlaceholderを実装する必要があるのはクラスBです。



//まとめ



執筆時点では、これはZKEntityでサポートされているすべての機能です。 上記をもう一度具体的な例としてまとめましょう。



イベントクラスがあるとします。









ZKEntityの実装は次のようになります。











ここに:





SyncIdKeyとchangeDateKeyは省略されました。 この例では、syncIDプロパティとchangeDateプロパティが対応しています。 同様のプロパティ(changeDate、syncID)は他のクラスのインターフェイスに存在するため、ZenCloudKit初期化フェーズ(後述)でユニバーサルとして記録されたため、プライベート実装は省略されました。



コントローラーとデリゲートのセットアップ



エンティティを構成したら、コントローラーを初期化してデリゲートを割り当てる必要があります。 これはさまざまな方法で行うことができますが、最良の方法は、このために別のクラスを用意して、呼び出し可能な初期化子を作成することです。



最初に、コントローラーの静的インスタンスへのリンクを格納するグローバル変数を作成できます。







デリゲートクラスは、次のプロトコルを実装する必要があります。











各メソッドを個別に検討する前に、完成した実装のバリエーションを見てみましょう(zenSyncDIdFinishメソッドを除く)。











例のCloudKitPresenterクラスはZenCloudKitデリゲートです。 これは、完全な同期サイクルを実装するために必要なコールバック関数の初期化と呼び出しが行われる場所です。 完全な同期サイクルは、ローカルおよびリモートのオブジェクトが変更時間と両端でのそれらの実現に関して比較されるオフスクリーン操作のシーケンスです。 このため、各タイプ、すなわち 登録された各ZKEntityエンティティに対して、フレームワークには、作成、IDによるオブジェクトのクエリ(フェッチ)、および使用可能なすべてのオブジェクトのクエリをそれぞれ実装する3つの関数を提供する必要があります。 3つの関数のそれぞれで、ZKEntityクラス(ofType T:ZKEntity.Type)はパラメーターとして機能します。 実行の結果、ZenCloudKitはこの特定のタイプのオブジェクトを受け取ることを期待しています。



zenAllEntities(Type T:ZKEntity.Type)

-タイプTのすべてのエンティティの配列を受け取ることを期待



zenCreateEntity(タイプT:ZKEntity.Type)

-Tの新しいインスタンスを受信する予定です。



zenFetchEntity(of T:ZKEntity.Type、syncId:String)

-指定されたsyncIdのTの既存のインスタンスを取得することを期待します(ない場合はnil)。



たとえば、PersonおよびHomeエンティティを操作する場合、これらの関数のTパラメーターはこれら2つのタイプのいずれかに等しくなります。 あなたの仕事は、それらのそれぞれに結果を提供することです(新しいオブジェクト、既存およびすべて)。 これは、型をチェックしてそれぞれのコードを記述するか、インターフェイスポリモーフィズムを使用して実行できます。



上記の例では、MagicalRecord標準メソッドを使用して、上記の操作を実行し、既存のメソッドを検索し、新しいメソッドを作成し、NSManagedObjectの拡張メソッド(またはObjective-Cの精神で表されるカテゴリメソッド)として機能するすべてのオブジェクトをクエリします。 これにより、実装が大幅に簡素化されます。 ケースTごとに型チェックを行う必要がないため、コードは汎用になります。



関数は一般的な抽象化の具体的な実装ですが、厳密に言えば、関数のシグネチャの一般化は、Objective-Cとの互換性を確保するために使用されません。



最後の関数は、T.predicateForId(...)ステートメントを使用します。 これはZenCloudKitが提供する拡張メソッドで、特定のsyncIdによって特定のタイプTの正しい検索述語を返します(IDをローカルに保存するプロパティの名前でハードコードとそれに関連するエラーを回避するため)。



zenEntityDidSaveToCloud(エンティティ:ZKEntity、レコード:CKRecord?、エラー:エラー?)

-CloudKitで保存が完了するたびに呼び出されます。 このフェーズでは、エンティティはすでにリモートオブジェクトのIDを受信して​​いるため、たとえば、メインデータベースコンテキストを保存できます。



デリゲートはプライベートシングルトンを実装します(sharedInstanceはクライアントには表示されません)。 コントローラとそのデリゲートの両方を初期化するには、適切なタイミングで外部からどこかでメソッドを呼び出すだけで十分です。







初期化メソッドでは、フレームワークが構成されます。











CloudKitのデフォルト設定が設定されています:





次に、すでに説明したsyncIdKeyキーとchangeDateKeyキーです。レコードIDとそれらが変更された日付を格納するプロパティの名前です。 これらの値は空白(nil)のままにできることに注意してください。 この場合、ZKEntityインスタンスで適切なメソッド(たとえば、save())を呼び出すと、ZenCloudKitは各クラスの宣言の中から実装を検索します。 逆に、特定の実装を省略するには、これらのキーをここでのみ指定するだけで十分です。 一般実装とプライベート実装の両方が空の場合、cloudKit.setup()の呼び出しはログにエラーをスローし、同期は機能しません。



entitiesパラメータには、使用するすべてのタイプの配列を渡します。



ignoreKeys-文字列キーの配列。検出時に、ZenCloudKitはオブジェクトを無視する必要があります(たとえば、保存または削除しないでください)。



deviceId-デバイスID。 複数のデバイスが同期に関係している場合、非常に重要なパラメーター。 開発者は、このパラメーターの一意性に注意する必要があります。 デフォルトでは、ハードウェアUUIDが使用されますが、他のオプションも可能です。



// RECAP



これまでに説明した設定の実装は、iCloudプロキシオブジェクトが提供する基本機能が機能するために必要かつ十分な条件であり、これがZKEntityFunctionsプロトコルを実装します。









update()関数を除き、その目的は、コード内でCKRecordとして表されるリモートオブジェクトからローカルオブジェクトを更新することです。 この関数は、フル同期サイクルの最後に呼び出される委任メソッドzenSyncDIdFinishで使用する必要があります。これは、次のように開始されます。









最初のオプションは、標準モードでの同期です。 以降の各同期サイクルは、ZenCloudKitによって修正されます。 成功した場合、最後の同期の日付が保存されます(フレームワークがこれをすべて処理します)。 日付を保存することは非常に重要です。変更日付が最後に成功したサイクルの日付より遅いオブジェクトのみを選択できます。 そうではなく、たとえばデータベースに100個のオブジェクトがある場合、各サイクルには、変更されずに長時間同期されているオブジェクトの無意味なチェックが含まれます。 これは完全に不要であり、さらにリソースを消費する操作です。



2番目のオプションは強制同期です(強制:true)。 データの整合性が損なわれる場合があります。 次に、最後に成功したサイクルの日付を無視して、同期された各オブジェクトを強制的にチェックし、ローカルおよびリモートでデータを更新できます。 ローカルオブジェクトはCloudKitにあるもので更新されます(何らかの理由でこれが以前に発生しなかった場合)。 また、CloudKitではローカルオブジェクトを保存できますが、これも何らかの理由で保存されませんでした。 アプリケーションの仕様に応じて、同期を強制する場所を自分で決定できます(たとえば、起動時、長時間の非アクティブ時、この機能を設定に使用するなど)。 一般的に、この呼び出しは不要であり、ほとんどの場合、それに頼る必要はありません。



コントローラーレベルでsyncEntities()メソッドを呼び出すと、登録されたすべてのエンティティに対してのみ同じことが行われます。 特定のパラメーターは、同期する特定のタイプを受け入れます(nil-すべてに適用する必要がある場合)。



zenSyncDIdFinishメソッドの解析は残り、そのシグネチャは次のようになります。









パラメータ:



Tは、オブジェクトを作成または更新するエンティティのタイプです。



newRecordsupdatedRecords - CKRecordの配列、ローカルで作成または更新する必要があるオブジェクト。 ローカルマッチングの検索における参照ポイントは一意のIDであり、CKRecord.recordID.recordNameプロパティに標準で保存されます。 オブジェクトを照合し、インスタンスを作成するエンティティはTです。



deletedRecords - ZKDeleteInfoオブジェクトの配列。各オブジェクトには、削除されたオブジェクトに関する情報(ローカルZKEntityタイプとオブジェクトID)が格納されます。 これらのオブジェクトにはさまざまなタイプがあるため、この場合はタイプTに焦点を合わせる必要はありません。 削除されたオブジェクトのタイプは、entityTypeプロパティで表示され、オブジェクトIDはZKDeleteInfoオブジェクトのsyncIdプロパティで表示されます。 クラスは次のとおりです。









ZenCloudKitは、deleteRecords配列のzenSyncDidFinishハンドラーに送信して削除を完了する前にこのリストを生成し、必要なローカルクリーンアップを実行できるようにします。 すべてがローカルで正常に削除されたら、コールバックメソッドfinishSync()を呼び出す必要があります。 これが行われない場合、CloudKitデータベースは変更されません。 このスキームは、セキュリティ上の理由から採用されました。ローカルデータベースが最新であることを確認した後にのみ、ファイナライザ-finishSync()を呼び出します。



同期の最後に必ずfinishSync()を呼び出してください。



これは、上記の削除フェーズだけでなく、作成および更新フェーズにも適用されます。



上記を要約するために、zenSyncDIdFinish関数の実装のフラグメントを検討します。









このフラグメントの直後に続く必要があります:



-finishSync()を呼び出す

-UI更新機能。データベースの変更された状態を反映します(必要な場合)。



次の指示を使用します。







ローカルオブジェクトのフィールドにCKRecordフィールドを入力します。これは、配列の1つの引数として使用できます。 fetchReferencesフラグを使用すると、すべてのリンクをロードできます。 リンクをロードするということは、CloudKitからの対応するオブジェクト(ZKEntityプロトコルで説明されているreferenceおよびreferenceLists配列にリストされている)の実際のロードと、このエンティティオブジェクトへのバインディングを意味します。 接続の読み込み時に、対応するローカルオブジェクトが存在しないことが判明した場合( zenFetchEntity == nil )、デリゲートメソッドzenCreateEntityを呼び出すことにより、ローカルデータベースに自動的に作成されます。



これらのリンクの形成にUIの変更が含まれる場合は、さらにこれに注意する必要があります(updateEntity-リンクを埋めるという点で-非同期に機能するため、完了するまで待たないでください)。 ZKRefListハンドラーではすでに述べたように、セッターでこれを行うことができます。









ここで次のことが起こります。



多くのリンク*(fetchReferences = trueフラグを指定してupdateEntityを呼び出した結果)へのリンクを受信すると、Teacherオブジェクトの配列がteacherReferencesのセッターに入ります。 メインスレッドで、ルートNSManagedObjectでこのリストを更新し、UI更新メソッドを呼び出します。



リンクのマッピング*-1(他のZKEntityエンティティへのプロパティリンクの名前を含む参照配列)にはハンドラー(取得/設定)が含まれないため、これらのリンクの形成を追跡する場合は、キーと同じメソッドを使用する必要があります参照配列でハンドラーを指定し、そのゲッターとセッターを再定義するには、ReactiveCocoaまたは他の手段を使用してプロパティを監視します。



リンクの操作はニュアンスに富んでいるようですが、これは事実ですが、これらのニュアンスは、CoreDataとCloudKitの2つのシステムのバインドと自動化の自然な結果です。



リンク、UIの更新、またはその他の同期関連プロセスをより直接制御する必要がある場合は、ZenCloudKitとネイティブCloudKit APIを自由に組み合わせることができます。 CKRecordオブジェクトの配列は、プロパティに加えてCKReferenceオブジェクトを含むzenSyncDidFinishメソッドで渡されます。 つまり、解析をカスタマイズしたり、必要なオブジェクトを手動でロードしたりできます。



これでZenCloudKitのセットアップが完了しました。



使用のニュアンス



フレームワークの機能にアクセスする標準的な方法は、ZenCloudKitコントローラーのインスタンス(シングルトン)を使用することです。









引数として-ZKEntityのすべての同じインスタンスとクラス。

(.iCloudプロキシクラスを介した)短縮バージョンは、現在Swiftでのみ利用可能です。



プッシュ通知



プッシュ通知処理は、ZenCloudKitに渡すこともできます。









彼の仕事の結果は、3つの満たされた配列(newRecords、updatedRecords、deletedRecords)の1つを持つデリゲートメソッドzenSyncDIdFinishの呼び出しになり、その実行は自動的にデータベースとUIの更新につながります(この関数の本体でこれを処理した場合)。 通常のプッシュ通知処理のシナリオには、かなり単調なアクションが多数含まれることを思い出してください:通知のタイプの確認(CKNotification)、通知の理由(queryNotificationReason)、解析-通知が関連するエンティティの決定と、対応するハンドラーの呼び出しのみです。 ZenCloudKitはこれらすべてを処理します。



同期ロック



遅かれ早かれ、アプリケーションコードはさまざまな場所で.save()または.delete()命令で満たされます。(システムプロパティではなく)アプリケーション内から同期を無効にする機能を想定している場合は、クライアントコードのすべての場所でフラグをチェックする代わりに、フレームワークレベルで同期を無効にできます。ご







想像のとおり、同期を再開するにはfalseを渡します。 。そして、コードはクリーンなままです。



ロギング



フレームワークのすべての主要な段階が記録されます。debugModeフラグを有効/無効にすると、コンソールへのサービス情報の出力を制御できます(デフォルトはtrue)。















コンテナのセットアップ:



操作を成功させるには、アプリケーションとZenCloudKitに、modifiedDateキー(CKRecord)へのクエリ権限を含む、使用されているすべてのレコードタイプへの読み取りおよび書き込みアクセスが必要です。これらすべてをダッシュ​​ボードに含めることを忘れないでください。さらに、データベース内のテーブルは、DeviceおよびDeleteQueueという名前で作成されます。最初のものには、データベースにアクセスする登録済みデバイスのリストが含まれます。 2番目の-削除キュー-は、各デバイスで削除する必要がある削除されたオブジェクトに関するメタ情報を持つテーブルです(各デバイス-対応するエントリ)。このデバイスが対応するオブジェクトをローカルで削除すると、DeleteQueueのレコードも消去されます。これらの2つのテーブルは公式であり、デバイスごとに完全な読み取りおよび書き込みアクセス権が必要です。



安全性



ZenCloudKitの仕事の最後の注目すべき瞬間はセキュリティです。

オブジェクトをCloudKitに保存するための手順は、通常2段階に短縮されます。(1)目的のオブジェクトの存在を確認し、次に(2)保存するだけです。最短時間で15個の新しいオブジェクトをアトミックに保存する(または同じものを連続して数回保存する)状況、またはこれが失敗の結果として発生する状況を考えます。標準のCloudKitシナリオでは、これは次のように発生する可能性があります。最初に、フェッチハンドルが数回動作し、nilを返し、次にsaveコマンドが同じ回数呼び出されます(結局、オブジェクトが見つかりませんでした)。その結果、CloudKitで同じオブジェクトの複数のインスタンスを取得することになります。 CloudKit APIは非同期ブロックに基づいているため、追加の手段(GCDを参照)がなければ避けられません。非同期ブロックのシーケンスは、CKQueryOperationの優先度とQoSフラグを設定しても予測が困難です。



上記のシナリオは、ZenCloudKitでは発生しないことが保証されています。ZenCloudKitは、初期化段階で、登録されたZKEntityタイプごとにキューを作成し、保存操作の実行の厳密なシーケンスを提供します。 15のオブジェクトの中に異なるタイプの3つのオブジェクト(合計5つのタイプ)がある場合、それらを「同時に」保存している間、5つのオブジェクトの保存プロセスが脅威なしで起動されます。また、このスキームは失敗の可能性(DoS)を無効にします。



おわりに



このフレームワークは、1人が約2か月間作成しました。ほとんどの時間は、プログラミングやリファクタリングよりもプログラミングに費やされていました。目標はシンプルでした。CloudKitとの典型的な同期操作のパフォーマンスを簡素化および統合し、CoreDataとの互換性の許容レベルを提供しました。現在までに、申請中に重大な不具合や重大なバグは発見されていません。



一部の機能については、この記事では説明していません(たとえば、失われた接続の管理、復元時に完全な同期サイクルを自動的に開始するなど)。いくつかのニュアンスも知られています。たとえば、現時点ではCKAssetsのサポートはありません(ただし、実装するのは難しくありません)。



現時点では、フレームワークとデモプロジェクトの計算の準備が進められています。ZenCloudKitのソースコードを受け取りたい場合、または質問やコメントがある場合は、この記事へのコメントまたはPMでそれらについてお問い合わせください。



All Articles