ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
CloudKitとCore Dataでデータを共有するAppの構築
NSPersistentCloudKitContainerを使用して、複数のiCloudユーザ間でデータを共有するAppを簡単に構築する方法について確認します。共有データをもとに情報豊富なエクスペリエンスを創り出す方法を確認し、Core Dataでこれらの機能をサポートするCloudKitテクノロジについても確認します。 このセッションを最大限に活かしていただくためには、NSPersistentCloudKitContainerに関する以前のビデオである、WWDC19の「CloudKitでCore Dataを使用する」とWWDC20の「CloudKitパブリックデータベースを用いてCore Data storeを同期させる」をご確認ください。
リソース
関連ビデオ
WWDC22
WWDC21
Tech Talks
-
ダウンロード
こんにちは アップルのCore Data チームのエンジニア ニック・ジレットです この動画では NSPersistentCloudKitContainer を使った Appを用いて 複数のiCloudユーザーで データを共有する方法を お見せします まずはじめに シェアとはなんでしょうか? NSPersistentCloudKitContainer が どのようにあなたの 生活に影響を与えるか 次にシェアの仕組みを 詳しく見ていきます 最後に NSPersistentCloudKitContainer が iCloudに保存された 情報のセキュリティを どのように守るのか 手短にご説明いたします まずはシェアについてから はじめましょう わかりやすく 例を出すと この写真を友達と シェアしたいと しましょう どんな方法があるだろうか Appleでは Appが作成したデータを シェアする方法が いくつかあります 写真Appのシェアシステム がすでに入っています 左下 アクションシートを開きます ここからシェアを含む 様々な操作が可能です 例えば iMessageやemailで 友達に送信できます しかし もし写真Appの 中で自動的に 友達と写真をシェアできたら いかがでしょうか? iCloudにサインインした 状態の時 このAppではもう一つの シェアの方法が使えます 共有アルバムです 共有アルバムは 他のユーザーも編集可能な アルバムのことです まずタイトルを付けます 次へを押します そして参加者を指定します ここでは アルバムを共有する 友達を4人選びました ヘザー ジャメイン パーシー メアリー シェア機能開発時の テストアカウントです 次へを押すと シェアした写真が新しい アルバムに追加されました 誰が参加しているか 確認することができます 右上にある パーソンアイコンを押すと 誰と共有しているのか 表示されます 招待中のアカウントと いくつかの設定が 表示されます どのようにこのシステムを 構築したか それより重要なのは このシステムが Appをどう改善するかです NSPersistentCloudKitContainer が シェア機能でどのような 役割をしているか お見せしましょう ここではサンプルの Appを使用して Core Dataとクラウドを 同期します すでにiCloudユーザーと 投稿をシェアできるよう 手を加えてあります こちらのテーブルに 様々なデバイスがあります それぞれ ヘザー ジャメイン パーシー メアリーの iCloudアカウントに ログインしてあります ジャメインのアカウントで 始めたいと思います 右上のプラス印を押し 新しい投稿を作成 わかりやすくタイトルは "シェアのデモは最高" 完了を押します
新しく追加された アクションボタンを押します 投稿をシェアできるよう メールで 全員に招待を送ります 先にメールを選択して 友達を選びます ヘザーとメアリーは アドレス帳に登録済なので 簡単に見つかります 送信を押して メールが送信されました
次にヘザーのデバイスで 先程のメールに添付された リンクを開きます 少し待つと ジャメインのデバイスで 作成した投稿が ここでも開く事が できるようになります
メアリーのデバイスも 同じです メールを開き リンクを開くと 先程の投稿が 少ししてから メアリーのデバイスでも 表示されました では これらはどのように 機能しているのでしょうか そして このサンプルに どれほどの変更を 加えたでしょうか 答えは "少し"です シェアはNSPersistent CloudKitContainerの中で 最も複雑な機能です CloudKitに関する 膨大な量のドメイン情報と 記録と投稿を 自然に操作できるか 共存が求められます もちろん ドメイン情報は KitContainer用に構築した APIに反映されています それでは 簡単なデモで NSPersistent CloudKitContainerが
どのシェアをしているか 見てみましょう ふたつのCloudKit データベースが 用いられています プライベート用と 共有用です どちらも パーシステント ストアに 複製されています 片方はプライベート用 データベースに紐付き もう一方は共有用 データベースに紐付きます これらを同時に 管理することで 二つを同時にAppで操作 可能となります この変更をもう少し 詳細にみてみましょう 最初の変更は NSPersistentCloudKitContainer に対し パーシステント ストアに データベースを複製する 指示を送ること CoreDataStackに 変更を加え パーシステント ストアを 新しく加え プライベート用にも 異なるURLで 同じものをコピーしました そしてオプションを シェア用に セットしました これが新しい iOS 15と NSPersistentCloudKitContainer が どのようにデータベースを 複製しているかでした このデモには あと二つの変更が必要でした
NSPersistentCloudKitContainer にシェア用の 新しいメゾットを追加 Share(_managedObjects:toshare: completion これが新しい部分です シェアコントローラーに 直接ペアリング出来ます シェアコントロールを インスタント化するため ボタンを追加しました Share(_managedObjects:to share:completion これはコントローラーの ワークフローが 呼び出されることを 意味します シェアされるべき ファイルを判別するため カバーの水面下で 必要な際は 多くの情報が やりとりされます 最後に INSPersistentCloudKitContainer から 提供された結果で コントローラーの 完了ブロックを呼び出します シェアの継続が可能なことを 教えてくれています これらが意味するのは 数行のコードで NSPersistentCloudKitContainer を使った シェアが可能ということです 最後の変更は 招待の受け入れを 可能にすることです ここでも NSPersistentCloudKitContainerを使った 新しいメゾットを 使用します acceptShareInvitationsfrom Metadata:intopersistentStore. このメソッドをAppDelegeateの Appで使用し userDidAcceptCloudKitShare Withmetadata method NSPersistentCloudKitContainer に直接 このメソッドで受信した メタデータを簡単に送信可能 このメソッドは 私がここで提供している App用の共有ストアである パーシステント ストアに 関連付けられたコンテナ内の CloudKitサーバとの 共有を受け入れます シェアが受け入れられると NSPersistentCloudKitContainerが デバイス内のストレージに 自動的に同期されます NSPersistent CloudKitContainerを用いて プライベート用と共有用 データベースで ファイルを共有 招待を受け入れる方法でした 私たちのAppは 共有されたデータを 用いたAppを容易に 作成するために 膨大な情報を 管理できるように なっています NSPersistentCloudKitContainerは これらオブジェクトを理解し ユーザーにとって有益な インターフェースを 構築できる 必要があります これらの課題をもう少し 明確に理解するために 2つの重要なコンセプトを 明らかにする必要があります
1つ目は、役割の概念です オーナーと 参加者と呼びます オーナーはデータを所持する iCloudアカウント オーナーがデータを作成し 参加者とシェアします 参加者はそれ以外の iCloudアカウントです これらのアカウントも同様に データを操作可能です 参加者は異なる役割や 許可を受けることが出来ます 特定のデータに対して操作を 制約可能です それが2つ目の キーポイントになります NSPersistentCloudKitContainerと CloudKitがどのように 機能するのか Core Dataでは NSManagedObjectの視点から データを捉えています NSPersistentCloudKitContainerは 管理されたデータを CloudKitに保存される CKRecordに変えます シェアに関して 知識のある方なら 階層的共有を ご存知だと思います これらの記録は シェアと呼ばれる ルートレコードに 関連付けられます NSPersistentCloudKitContainer は それとは異なります Record Zone シェア という新機能を CloudKitに搭載しました 詳しくは"Cloudkitの新機能" という動画をご覧ください NSPersistentCloudKitContainerの Record Zoneを使った データ管理をご覧ください CloudKitデータベースには-- 例えば プライベートデータベース-- NSPersistentCloudKitContainerが Appが制作したデータを プライベートデータとして 管理します Record Zone シェアでは 共有されたCKRecordsが CKRecordZoneに含まれる 共有されたRecord Zoneは CKShare recordの 存在によって識別される 階層的共有と同じく この記録が オーナー 参加者 許可など 必要な情報を 保持しています NSPersistentCloudKitContainerは これらを自動的に記録し 同様にゾーンも管理します NSPersistentCloudKitContainer には ルートレコードが無いため オーナーや参加者 といった役割を 理解している必要があります データをシェアしたいと 人々が-- いるとしましょう 1人とのシェアは 興味深いです しかしながら NSPersistentCloudKitContainerは 大人数とのシェア用に デザインされています 参加者一人一人が 共有されたデータに アクセスし 変更を加えることも可能で 彼らが共有したデータに 私がアクセスし 変更を加えることも出来ます 彼らは様々なデバイスを 使用しているでしょう NSPersistentCloudKitContainer なら 参加者は どのAppleデバイスからでも アクセス可能です それぞれの参加者に NSPersistentCloudKitContainer が 2つのデータベースを 作成します プライベート用と 共有用です 私のプライベート データベースでは 共有されているかは 関係なしに 記録とゾーンを閲覧可能です 例えば NSPersistentCloudKitContainer が 管理しているゾーン 共有用では NSPersistentCloudKitContainer が CKShare recordを用いて 共有ゾーンを 作成します これが私のゾーンを 誰が閲覧可能か コントロールし 許可されたユーザーが 共有ゾーンに変更を 加える事ができます 私の共有用 データベースでは 他のユーザーが私と共有した ゾーンを見る事ができます 許可されていれば これらのゾーンに 変更やデータの追加が 行えます 先ほどの例と同じようにです
他のユーザーは それぞれ プライベート用 共有用で それぞれ 別のゾーンに アクセス可能です 例えば このユーザーは プライベート用と 他のユーザーのデータを 共有用ゾーンで記録します さらに 条件によっては 彼らは私の二つのゾーンに アクセスすることが可能です それでは NSPersistentCloudKitContainerは どのようにデータ保存場所を 決めているのでしょうか? ほとんどの場合は データをユーザーの関係で 決めることができます しかし同様に以下のコード share(_managedObjects: toShare:completion これで特定のゾーンに データを 保存することができます 例えば このコードを既に存在する 共有ファイルに変えると NSPersistentCloudKitContainer が データを共有ゾーンに 移します これらは最初のデモの為に 私が加えた変更です しかし より効果的な 操作のためには どのデータが共有用か 誰とか 参加者が何を変更可能かを 指定する必要があります ユーザーは全ての情報を 手に入れることで どのデータをシェアするか 間違いのない選択が 可能になるでしょう これらの状態や 権限を与えるために Appにどのような変更を 加えたか見てみましょう サンプルのAppに戻ると 最初のデモのデータが シェアされたことを意味する 新たなインターフェース と共に 表示されています タップすると他のユーザーが 役割と招待受入状態と共に 下の方に表示されます ジャメインが投稿の オーナーであり ヘザーが参加者だと 確認できます 今から 新しい投稿をし タイトルを決め 完了
シェアコントローラーを読込 シェアしますが 今回は閲覧のみ 投稿に対する 変更不可で共有します 共有オプションを開き 閲覧のみを選択します 次にメールを開き ジャメインとメアリーを招待
メールを送ります
メアリーのデバイスで シェアを受け入れ 投稿が開けました タップして 右上を確認すると 編集はボタンは押せません 参加者の欄でもメアリーは 読み込みのみの 参加者と表示されます 同じく スワイプで投稿を削除 することもできません テーブルを開き エディットモードからも 削除はできません
ジャメインのデバイスで シェアを受け入れます
投稿が表示されました タップすると エディット不可に なっています 参加者欄には ジャメインが 読み込みのみ可能と 表示されます
この簡単な サンプルAppでも インターフェースに いくつかの変更があります 共有されたデータの 情報を表示するためです どの投稿が共有されているか 表示するために テーブルの表示を変え 編集の可否を決定する ロジックを追加 それを 参加ユーザーのステータスに 合わせて行きます そして 最後に それぞれのシェアに 合わせて 参加者の情報を表示する インターフェースを 構築しました これら全てはCKShareに 関するメタデータに アクセスし投稿の保存場所に アクセスする必要があります NSPersistentCloudKitContainer には これらの問題解決のため APIメソッドが含まれます FetchSharesMatchingObjectIDsは iOS15に新たに追加され 特定の投稿のCKShareへ アクセスを可能にします しかしこれら3つの メゾットは 2020年のWWDCで既に 紹介されています これらのメゾットをApp内 どこでも インターフェースの 変更に使えます しかし 私のAppでは 少し異なる方法を使います NSPersistentCloudKitContainer を 直接呼び出すのではなく 必要なカスタマイズごとに 特定のメソッドを公開する プロトコルを構築しました SharingProviderと 名付けました これはApp内で 特定のサイトを 直接結びつける事が 可能です 例えば データがシェアされているか 否かを知りたい時 シェアされていた場合 ユーザーインターフェースに 詳細な情報を表示するため そのデータのCKShareや パーシステントを 取得する必要が あるかもしれません 最後に データは常にミュート可能 ではありません 同じデータに対し ユーザーが 異なる許可を持っています このプロトコルは 応用コードに ロジックを追加するのを 簡単にします 実際に MainViewControllerで in the MainViewController, 試してみましょう シェアされているか否か ここでわかります ここでは"isShared"を使って 投稿のタイトルを属性付きの 文字列に変換し 投稿が共有の一部で あることを示すために "person.circle"のシンボルを 前に付けるかどうか 決めています このようなカスタマイズは 共有しない場合より 複雑なコードが 必要になります これらのカスタマイズを 追加した後 言うまでもないですが 全てが正しく動作することを 確認する必要があり それが SharingProviderが存在する 最大の理由であるテストです SharingProviderのプロトコルは インジェクションによって これらの決定ポイントを 簡単にテストできます このコードは MainViewControllerの テーブルセルが正しく 表示されることを 確認するために書いた テストケースの一部です サンプルデータ作成の 準備は省略しましたが このテストでは 管理された データの混合セットを作成 このセットに含まれる objectIDの存在によって 共有されているか どうか識別します 次にインスタンスを 設定します テスト用に書かれたクラスで logicMainViewControllerが 使用する SharingProviderに カスタムロジックを 簡単に注入できます ここでは 作成したセットの containsメソッドを 呼び出すように isSharedBlockを設定し Swiftのトリックで 与えられたobjectIDが sharedObjectIDsの中に あるかチェックできる それから BlockBasedProviderを ビューコントローラの プロバイダとして設定し インジェクションを完了 テストの最後に MainViewControllerに テーブルセルを 要求しています SharedObjectIDsセットに 含まれるものは 想定通りの接頭を持ち 非共有オブジェクトに 対応するセルは 持ってないことを確認します isSharedの実行は 私のApp内の PersistentCloudKitContainerを 管理する CoreDataStackに あります テストで使ったシンプルな インジェクションよりも 少し複雑になっているのが わかると思います。 この操作の実行を 1行ずつ見てみましょう しかし それは そこまで重要ではありません ここで重要なのは 簡単に操作しましたが 実際はもっと 複雑ということです テーブルビューの変更を 検証したいとき 毎回 そうしようとすると 開発プロセスに 大きな摩擦が生じます その一方で インジェクション技術で サーバーを 経由することなく 共有オブジェクトの さまざまな構成を 簡単かつ迅速に テストできます これらのテストを すべて記述し インジェクションを容易にし Appを構成するには 前もって 少しだけコードが必要です しかし 結果として得られる 信頼性は価値があります 先に述べたように SharingProviderには サンプルAppのための 重要なメソッドが 他にも多数含まれています それらの実行と インターフェースに どのような影響を 与えるかについて テストをチェックすることを お勧めします 合計で 1200以上のコードを 追加しました これらあなたのApp開発が 楽になることを 願っています 今日 最後に お話しするのは CKRecordの値として 暗号化された Cloudkitの新機能への サポートです これらの値は "encryptedValues"という CKRecordの新しい ペイロードに保存されます "Cloudkitの新機能"の項目で 紹介されています このCKRecordの 新しいペイロードは ユーザーのキーチェーンに あるキー素材を使って 値を暗号化 することができます CloudKitサーバーから ダウンロードされた後 デバイス上で ローカルに復号されます ローカルに 暗号化されてから CloudKitサーバーに アップロードされます Xcodeでワンクリック するだけで 暗号化された値の選択を 可能にしました 実際にやってみましょう
Xcodeで サンプルApp "Syncing a Core-- Data Store with Cloud"を 開きました CoreDataCloudKitDemoの マネージドオブジェクト モデルを開いてみます そして post entityには locationという 特定のプロパティがあるので それを紹介します 位置属性を 選択すると 右のデータモデル インスペクタに その構成が 表示されます オプションの 変形属性に設定し 新しいAllows Cloud Encryption チェックボックスに チェックを入れました この新しい チェックボックスは NSPersistentCloudKitContainer に この属性の値が結果として 得られるCKRecordの 暗号化Valuesペイロードに 保存されるべきで あることを伝えます マトリックスをコードで 読みたい場合は NSAttributeDescriptionの CloudEncryptionを 許可する新しいブール値が あるので これを使ってモデルコードで プロパティを 設定することができます CloudKitでの暗号化は 導入時の判断になります。 つまり 後から考えを変えて 暗号化されていない フィールドを 暗号化するように 選択することはできません 同様に すでに暗号化されている フィールドを 暗号化解除することも できません CloudKitスキーマが 本番にプッシュされると フィールドタイプは 一切変更できません。 そのため NSPersistentCloudKitContainer initializeSchemaメソッドを 使用して すべてのフィールドが 正しく型付けされているか スキーマをプロダクションに デプロイする前に 確認してください
シェアをサポートするために NSPersistentCloudKitContainer に加えられた 変更点の一部を ご紹介できて光栄です 新しいAPIに関して 学ぶことがたくさんあります そこで サンプルAppと ドキュメントを更新し Appでの使用方法を 説明しました これには、CloudKitを 使用する際にオブジェクトが 取り得るさまざまな状態に Appが どのように対応するか 検証するためのテストの-- 記述方法も含みました また 何か問題が 発生した場合は 報告してください いつも通り NSPersistentCloudKitContainer みなさんがどのように 活用するかが楽しみです ぜひWWDC2021を 最大限お楽しみください [音楽]
-
-
5:20 - Add shared store description
let privateStoreDescription = container.persistentStoreDescriptions.first! let storesURL = privateStoreDescription.url!.deletingLastPathComponent() privateStoreDescription.url = storesURL.appendingPathComponent("private.sqlite") privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) let sharedStoreURL = storesURL.appendingPathComponent("shared.sqlite") let sharedStoreDescription = privateStoreDescription.copy() sharedStoreDescription.url = sharedStoreURL let containerIdentifier = privateStoreDescription.cloudKitContainerOptions!.containerIdentifier let sharedStoreOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: containerIdentifier) sharedStoreOptions.databaseScope = .shared sharedStoreDescription.cloudKitContainerOptions = sharedStoreOptions container.persistentStoreDescriptions.append(sharedStoreDescription)
-
6:00 - shareNoteAction, DetailViewController.swift
@IBAction func shareNoteAction(_ sender: Any) { guard let barButtonItem = sender as? UIBarButtonItem else { fatalError("Not a UI Bar Button item??") } guard let post = self.post else { fatalError("Can't share without a post") } let container = AppDelegate.sharedAppDelegate.coreDataStack.persistentContainer let cloudSharingController = UICloudSharingController { (controller, completion: @escaping (CKShare?, CKContainer?, Error?) -> Void) in container.share([post], to: nil) { objectIDs, share, container, error in if let actualShare = share { post.managedObjectContext?.performAndWait { actualShare[CKShare.SystemFieldKey.title] = post.title } } completion(share, container, error) } } cloudSharingController.delegate = self if let popover = cloudSharingController.popoverPresentationController { popover.barButtonItem = barButtonItem } present(cloudSharingController, animated: true) {} }
-
17:06 - SharingProvider
protocol SharingProvider { func isShared(object: NSManagedObject) -> Bool func isShared(objectID: NSManagedObjectID) -> Bool func participants(for object: NSManagedObject) -> [RenderableShareParticipant] func shares(matching objectIDs: [NSManagedObjectID]) throws -> [NSManagedObjectID: RenderableShare] func canEdit(object: NSManagedObject) -> Bool func canDelete(object: NSManagedObject) -> Bool }
-
17:58 - Decorate table cells for shared posts, MainViewController.swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as? PostCell else { fatalError("###\(#function): Failed to dequeue a PostCell. Check the cell reusable identifier in Main.storyboard.") } let post = dataProvider.fetchedResultsController.object(at: indexPath) cell.title.text = post.title cell.post = post cell.collectionView.reloadData() cell.collectionView.invalidateIntrinsicContentSize() if let attachments = post.attachments, attachments.allObjects.isEmpty { cell.hasAttachmentLabel.isHidden = true } else { cell.hasAttachmentLabel.isHidden = false } if sharingProvider.isShared(object: post) { let attachment = NSTextAttachment(image: UIImage(systemName: "person.circle")!) let attributedString = NSMutableAttributedString(attachment: attachment) attributedString.append(NSAttributedString(string: " " + (post.title ?? ""))) cell.title.text = nil cell.title.attributedText = attributedString } return cell }
-
18:44 - Testing the MainViewController's table view, TestMainViewController.swift
func testSharedPostsGetDisclosure() { var sharedObjectIDs: Set<NSManagedObjectID> = Set() let context = coreDataStack.persistentContainer.viewContext self.generatePosts(in: context, postSaveBlock: { posts in for (index, post) in posts.enumerated() where (index % 4) == 0 { sharedObjectIDs.insert(post.objectID) } }) let provider = BlockBasedShareProvider(stack: coreDataStack) provider.isSharedBlock = sharedObjectIDs.contains mainViewController.sharingProvider = provider do { try mainViewController.dataProvider.fetchedResultsController.performFetch() } catch let error { XCTFail("Error while fetching \(error)") } reloadTableView() let rowCount = mainViewController.tableView(mainViewController.tableView, numberOfRowsInSection: 0) XCTAssertEqual(100, rowCount) guard let expectedSharedImage = UIImage(systemName: "person.circle") else { XCTFail("Failed to get the person system image.") return } for index in 0..<rowCount { let indexPath = IndexPath(row: index, section: 0) let post = mainViewController.dataProvider.fetchedResultsController.object(at: indexPath) guard let title = post.title else { XCTFail("All posts should have been given a title.") return } guard let cell = mainViewController.tableView(mainViewController.tableView, cellForRowAt: indexPath) as? PostCell else { XCTFail("Encountered an unexpected cell type in the main view controller's table view.") return } if sharedObjectIDs.contains(post.objectID) { guard let attributedText = cell.title.attributedText else { XCTFail("Failed to get the attributed text of \(cell). Was it not set?") return } guard let attachment = attributedText.attributes(at: 0, effectiveRange: nil)[.attachment] as? NSTextAttachment else { XCTFail("Expected an image attachment at the first character.") return } XCTAssertEqual(expectedSharedImage, attachment.image) } else { XCTAssertEqual(cell.title.text, title) } } } class BlockBasedShareProvider: SharingProvider { var coreDataStack: CoreDataStack init(stack: CoreDataStack) { coreDataStack = stack } func isShared(object: NSManagedObject) -> Bool { return isShared(objectID: object.objectID) } public var isSharedBlock: ((_ object: NSManagedObjectID) -> Bool)? = nil func isShared(objectID: NSManagedObjectID) -> Bool { guard let block = isSharedBlock else { return coreDataStack.isShared(objectID: objectID) } return block(objectID) } public var participantsBlock: ((_ object: NSManagedObject) -> [RenderableShareParticipant])? = nil func participants(for object: NSManagedObject) -> [RenderableShareParticipant] { guard let block = participantsBlock else { return coreDataStack.participants(for: object) } return block(object) } public var sharesBlock: ((_ objectIDs: [NSManagedObjectID]) -> [NSManagedObjectID: RenderableShare])? = nil func shares(matching objectIDs: [NSManagedObjectID]) throws -> [NSManagedObjectID: RenderableShare] { guard let block = sharesBlock else { return try coreDataStack.shares(matching: objectIDs) } return block(objectIDs) } public var canEditBlock: ((_ object: NSManagedObject) -> Bool)? = nil func canEdit(object: NSManagedObject) -> Bool { guard let block = canEditBlock else { return coreDataStack.canEdit(object: object) } return block(object) } public var canDeleteBlock: ((_ object: NSManagedObject) -> Bool)? = nil func canDelete(object: NSManagedObject) -> Bool { guard let block = canDeleteBlock else { return coreDataStack.canDelete(object: object) } return block(object) } }
-
20:01 - CoreDataStack + Sharing, CoreDataStack.swift
extension CoreDataStack: SharingProvider { func isShared(object: NSManagedObject) -> Bool { return isShared(objectID: object.objectID) } func isShared(objectID: NSManagedObjectID) -> Bool { var isShared = false if let persistentStore = objectID.persistentStore { if persistentStore == sharedPersistentStore { isShared = true } else { let container = persistentContainer do { let shares = try container.fetchShares(matching: [objectID]) if nil != shares.first { isShared = true } } catch let error { print("Failed to fetch share for \(objectID): \(error)") } } } return isShared } func participants(for object: NSManagedObject) -> [RenderableShareParticipant] { var participants = [CKShare.Participant]() do { let container = persistentContainer let shares = try container.fetchShares(matching: [object.objectID]) if let share = shares[object.objectID] { participants = share.participants } } catch let error { print("Failed to fetch share for \(object): \(error)") } return participants } func shares(matching objectIDs: [NSManagedObjectID]) throws -> [NSManagedObjectID: RenderableShare] { return try persistentContainer.fetchShares(matching: objectIDs) } func canEdit(object: NSManagedObject) -> Bool { return persistentContainer.canUpdateRecord(forManagedObjectWith: object.objectID) } func canDelete(object: NSManagedObject) -> Bool { return persistentContainer.canDeleteRecord(forManagedObjectWith: object.objectID) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。