大多数浏览器和
Developer App 均支持流媒体播放。
-
构建通过 CloudKit 和 Core Data 共享数据的 app
了解如何使用 NSPersistentCloudKitContainer 轻松地构建可在多个 iCloud 用户之间共享数据的 app。探索如何围绕共享数据带来融入丰富信息的体验,并了解支持 Core Data 中的这些功能的 CloudKit 技术。为了能充分了解本节内容,请观看我们之前有关 NSPersistentCloudKitContainer 的视频:WWDC19 的“将 Core Data 和 CloudKit 配合使用”,以及 WWDC20 的“将 Core Data 存储与 CloudKit 公共数据库同步”。
资源
相关视频
WWDC22
WWDC21
Tech Talks
-
下载
嗨 我是尼克吉列特 Apple Core Data 团队 的工程师 在本环节中 我将展示如何通过 NSPersistentCloudKitContainer 打造应用 与多位iCloud用户 共享信息 首先 我们先探讨何谓共享 以及 NSPersistentCloudKitContainer 还有它能在开发的体验上 带来何种影响 接下来 我们将深入了解共享的机制 以及最后 我们会迅速谈论一下 NSPersistentCloudKitContainer 如何对iCloud上存储之隐私数据 启用额外的保护 让我们从共享谈起 为了方便讨论 想象一下我要把这张照片 共享给我的朋友们 我可以怎么做呢? 在Apple平台上有几个方法 能让我们共享自应用生成的数据 “照片”采用了一个系统控制 在左下角 可以让你呼出操作列表 里面有各式各样可用的操作 包含各种共享的方式 例如 我能通过iMessage 或电邮发送给我朋友 但理想情况下 我们在照片应用里 会有个独立的区域 让我和朋友可以互相共享相片 当我登入iCloud后 “照片”还支持了另一种共享选项 共享相册 照片里的共享相册 会创建一个共享的图片集 供其他用户检视 若有需求 也可协力编辑 我为新建相册输入了标题 再点击下一步 来选择协作者 在这里我选择了四个朋友 与我共享相册 海瑟、杰梅因、派西 和玛丽全都是测试账户 在我打造共享功能时常会用到 当我点击下一步后 我就能看到我共享的新相册 照片也允许我检视该相册的协作者 通过点击右上方的人形图标 点击后会呼出协作者的清单 在这里能看见他们的邀请状态 以及共享相簿的一些权限 我们该如何打造这种体验? 而更重要的是 这样的体验会如何 改变我们打造的应用? 让我为你们展示一下 为了演示共享是如何通过 NSPersistentCloudKitContainer运行的 我会使用我们的范例应用 与云端的核心数据存储同步 经过我的修改 它已能支持 与不同的iCloud用户共享帖子 而在这张桌上 我有几台设备 每一台都登录了iCloud账户 分别属于海瑟、杰梅因和玛丽 我现在要在杰梅因的设备上 启动我的应用 点击右上角的加号 来创建新帖子 输入一个简单的标题 “共享测试棒极了” 然后点击完成
然后再点击我新加入的操作表 来叫出共享控制项 我想通过电邮对每个人 发送协作帖子的邀请 所以我会点击邮件 然后输入朋友的信息 海瑟和玛丽已经在我的电话本里了 所以很简单就能找到 最后点击发送 送出电邮
现在 在海瑟的设备上 打开邮件 然后点击 我送出的电邮中的链结 就会开启我的应用 经过短暂的等待 我在杰梅因的设备上创建的帖子 在这台设备上也看得到了
在玛丽的设备上 打开邮件 点击我发送的电邮 就会开启我的应用 经过短暂的等待 我在杰梅因设备上创建的帖子 现在在这台设备上也看得到了 但这一切是如何做到的呢? 以及在这个范例应用中 我得做出多少改动? 答案是:“不多” 共享是目前最复杂的功能 作为NSPersistentCloudKitContainer 组成的一部分 它集结了巨量的领域知识 有关CloudKit的运作原理 以及在记录和对象上如何运作 当然 这样的领域知识 也在我们为 NSPersistentCloudKitContainer 打造的API中体现了出来 所以来看看到底 NSPersistentCloudKitContainer 是如何共享对象的
在我短暂的演示中 我展示的这个应用程序 利用了两个CloudKit数据库 私有及共享数据库 每一个都在我的应用中 作为持久化存储的镜像 其中一个使用私有数据范围 另一个则使用共享数据范围 通过单一管理的对象上下文 我的应用就能访问两个存储的数据 让我们来更仔细看看这个改变 我必须做的第一个改变是 就是告知 NSPersistentCloudKitContainer 将CloudKit共享数据库 以持久化存储镜像 通过修改CoreDataStack 加入新的持久化存储描述… 这边就有一个共享存储的副本 有着不同的URL 接着我把它的 CloudKitContainerOptions databaseScope属性 设定为“shared” 这就是全新的iOS 15 而且允许配置 NSPersistentCloudKitContainer 对共享CloudKit数据库 做持久化镜像存储 为了完成这个演示 我还得做出两项更动
我在NSPersistentCloudKitContainer 上采取了新方法 来创建共享 Share(_managedObjects:toshare: completion 这个新方法 是为了和UICloudSharingController 直接配对所设计 我加入了按钮动作 来举例说明 UICloudSharingController Share(_managedObjects:to share:completion 必须在UICloudSharingController 工作流中的 创建-共享阶段就被调用 它会暗中处理很多事务 辨识出所有的对象 是否需要共享 接着如果需要的话 就会创建一个共享出来 最后 我调用了 UICloudSharingController的 完成模块 以及 NSPersistentCloudKitContainer 所提供的结果 这样我就知道我已经准备好 继续共享的流程 这代表你可以通过 NSPersistentCloudKitContainer 共享对象 只需要靠几行代码 我要做的最后一项更动 就是要能够接受共享邀请 因此我会用这个 在NSPersistentCloudKitContainer 上的新方法: acceptShareInvitations(from Metadata: intopersistentStore: 我在AppDelegeate的应用中 使用这个方法 userDidAcceptCloudKitShare Withmetadata方法 来直接将到来的共享元数据 传送给 NSPersistentCloudKitContainer 这个方法会通过 我所提供的 与持久化存储 连结的容器中的 CloudKit服务器接受共享 就在这里 我的应用的共享存储 在共享被接受后 NSPersistentCloudKitContainer 会将所有的 共享对象自动同步到本地存储中 这就是我们利用 NSPersistentCloudKitContainer 来合并私有以及共享数据库的原理 为对象创建共享 以及接受共享邀请 但我们的应用程序经常是 为了管理大型数据集合而打造 为了更容易打造 使用这些共享数据的应用 NSPersistentCloudKitContainer 也帮助了我们搞清楚 所有这些对象 因此我们就能为用户 打造直观的用户界面 为了更清晰地了解这些挑战 我们得认识共享的两个重要概念才行
第一个概念是actor的组合 我们称之为拥有者与参与者 拥有者就是拥有对象的iCloud帐户 拥有者创建、并与一群 参与者共享对象 参与者就是其他任何iCloud账户 且被允许某程度上运行那些对象 参与者会具备不同的角色与权限 来限制他们能如何影响特定的对象 我们也因此来到了第二个关键概念: NSPersistentCloudKitContainer CloudKit是如何构建 这些共享对象的 在核心数据中 我们根据NSManagedObject 来思考我们的对象 NSPersistentCloudKitContainer 将这些托管对象 转换为存储在CloudKit中的 CKRecord实例 如果你以前使用过共享 那么你可能熟悉分层共享 其中与这些记录相关联的根记录 被称之为“共享” 但这并非 NSPersistentCloudKitContainer 运作的方式 NSPersistentCloudKitContainer 利用了CloudKit中的新功能 称为“记录区域共享” 而这在 《CloudKit的新功能》说明中 有详尽的介绍 但是我们来看看 NSPersistentCloudKitContainer 是如何利用Record Zone Sharing 来共享托管对象的 在CloudKit数据库中 例如说.private数据库 NSPersistentCloudKitContainer 通常会管理一块私有区域 来存储应用程序创建的对象 在Record Zone Sharing中 共享CKRecord则是包含在 共享CKRecordZone之中 共享记录区域是通过 单个CKShare 记录的存在来标识的 与分层共享一样 此记录包含所有必要的 与区域合作的信息 例如拥有者 参与者及其权限和角色 NSPersistentCloudKitContainer 会管理这些区域 并自动为它们分配记录 因为没有根记录 NSPersistentCloudKitContainer 还必须了解所有者和参与者的概念 要如何应用到整个记录区上 假设我有一群 想共享的人 和一个人共享很有趣 但NSPersistentCloudKitContainer的 设计理念就是促进 与更广大人群之间的共享 这些参与者中的每一个人 都将能够访问 和操作我与他们共享的对象 我也能够访问 和操作他们 与我共享的对象 每个参与者都有自己的 设备集合 通过NSPersistentCloudKitContainer 应用程序可以从任何Apple设备 对共享对象进行操作 对于每个参与者 NSPersistentCloudKitContainer 负责管理两个CloudKit 数据库中的对象 .private和.shared数据库 在我的.private数据库中 无论这些区域是否共享 我都能看到我拥有的记录和区域 例如说 经由 NSPersistentCloudKitContainer 管理的区域 通过共享 NSPersistentCloudKitContainer 也会通过CKShare记录 为我创建共享区域 并可以控制谁能访问 我所拥有的这些区域 如果允许的话 我和其他参与者 可以在这些共享区域中 添加和修改记录 在我的.shared数据库中 我会看到其他用户 与我共享的记录区域 如果允许的话 我可以将我拥有的记录 添加到任何这些区域 就像他们在我的区域中能做的一样
另一个用户会在他们的 .private和 .shared数据库中 看到一组不同的区域 这取决于他们是否 为这些区域的拥有者 例如 此用户可能在 其.private数据库中 各拥有一个私有区域和一个共享区域 此外 他们会看到我与他们共享的两个区域 以及他们在该.shared数据库中 所参与的任何其他区域 那么 NSPersistentCloudKitContainer 如何知道你的记录保存在哪呢? 在多数情况下 它可以基于与其他对象的关系 推断出记录所属的位置 但是你也可以告诉 share(_managedObjects: toShare:completion 通过传递一个非空值的CKShare 来将对象存储在特定共享区域中 例如 如果我将这行代码更改为 使用现有共享 NSPersistentCloudKitContainer 会将准备好的帖子对象分配给该共享 我为了这个演示 做了这些更动 但我的应用程序还需要有效地沟通 哪些对象要共享、与谁共享 以及这些参与者可以做什么 我们的用户需要所有这些信息 以便他们能够对选择共享的对象 做出正确的决定 那么来看看我如何更改我的应用程序 来传达这些状态和特权 回到应用程序范例 我可以看到 第一个演示的数据现在出现了 还有一些新的用户界面装饰 来表示帖子已被共享 如果我点击它 我还可以看到 详细视图控制器底部显示的参与者 以及他们的角色、权限和接受状态 我们可以在这里看到杰梅因 是该帖子共享的所有者 而海瑟是私人参与者 现在我要添加一篇新帖子 放上一个标题 然后点击完成
我将点击操作按钮 以调出共享控制器 但这次 我希望该共享是只读的 使参与者无法编辑 或修改共享内容 因此 我会更改共享选项 将共享标记为仅查看 接下来 我将点击邮件并邀请 杰梅因和玛丽
最后 我会发送电子邮件
在玛丽的设备上 我会接受新的共享 现在我可以看到新的帖子了 如果我点击它 我可以看到“编辑”按钮已禁用 并且玛丽的参与者条目显示 她是这个共享中的 只读参与者 同样 我无法通过滑动 来删除这篇文章 如果我将表格视图置于编辑模式 通过点击“编辑”按钮 我无法删除这篇帖子
现在在杰梅因的设备上 我要接受新共享
现在我可以看到新帖子了 如果我点击它 我可以看到“编辑”按钮已禁用 并且杰梅因的参与者条目显示 他是共享的只读参与者
即便在这个不起眼的范例应用程序中 我还是得对用户界面进行一些更改 来显示有关共享对象的信息 我也得装饰帖子表格单元格 来标示哪些是被共享的 我还必须添加逻辑 来启用或禁用编辑控件 而这取决于分配给 当前用户参与者的权限 最后 我必须构建新的用户界面元素 以在个别共享上显示 有关参与者的信息 这些工作都需要访问一些有关 特定帖子所在的 CKShare的元数据 NSPersistentCloudKitContainer 包含了 许多API方法来满足这些问题 FetchSharesMatchingObjectIDs 是iOS 15中的新功能 它允许我获取特定帖子的CKShare 但是这三种条件化编辑方法 在2020年的WWDC上 与我们的公共数据库支持 就介绍过了 你可以在你的应用程序中 使用这些方法来自定义你的用户界面 但在我的应用中 我采取了稍微不同的方法 而不是直接调用 NSPersistentCloudKitContainer 上的方法 我构建了一个协议 为我需要的每个自定义功能 揭开了一个特定的方法 它被称为共享提供者 共享提供者有个方法 可以在应用中直接绑定到 特定呼叫位置 例如 我可能需要知道一个对象是否被共享 如果是被共享的 我可能需要获取该对象的 CKShare或参与者 并在我的用户界面中显示更多信息 最后 对象可能不会一直是可变的 并且各个参与者对同一对象 可以拥有不同的权限 这个协议让我更容易 把特定逻辑添加进应用程序代码中 我想在MainViewController中 向你展示一个特定的呼叫位置 让我能知道一个对象是否被共享 在这边我使用了isShared 来决定是否把帖子的标题 转换成一个属性字符串 并在它前面加上 person.circle符号 来表明该帖子是共享的一部分 与我选择不支持共享相比 像这样的自定义 必然需要更复杂的代码 添加一些这些自定义后 它变得很明显 我需要一种方法 来确保它们都正常工作 这也就让我想到 SharingProvider 存在的主要原因:测试 SharingProvider协议 让人更容易通过注入 来测试这些决策点 这段代码是我为 MainViewController编写的 测试例子的一部分 以确保其表格单元格正确表示 帖子是否已共享 我移除了用于创建范例数据的鹰架 但测试制作了一组混合的托管对象 且它通过了 在该集合中objectID的存在 来标识为共享或不共享 接下来我配置了一个 BlockBasedShareProvider 的实例 一个专门 为测试而编写的类别 这使我可以轻松地将自定义逻辑 注入MainViewController 所使用的sharedProvider 这里我设置了isSharedBlock 来调用我创建集合的contains方法 这是Swift的小技巧 它允许能我简单地检查 已提供的objectID是否在 sharedObjectID之中 然后 我将BlockBasedProvider 设置为视图控制器的提供者 来完成注入 最后 这个测试会对 MainViewController 要求它的表格单元格 验证那些被我纳入在 sharedObjectIDs 集合中的、有着预期前缀的单元格 而对应于未共享对象的单元格 则是没有 isShared真正的实现 (implementation) 是在CoreDataStack之中 而它负责为我的应用程序管理 PersistentCloudKitContainer 你可以看到 它比我在测试中 使用的简单注入 又更复杂一些 现在 我可以一行一行地 来检查这个实现 但这不是最重要的 重点是它复杂多了 不是在我每次想验证 对表格视图的更改时 能够轻松操作的 试着这样做会对开发过程 产生很多阻力 另一方面 这种注入技术 无需与CloudKit服务器通信 即可轻松快速地测试 共享对象的不同配置 需要这么多的代码 才能够编写所有这些测试 并构建应用程序 而且是通过促进这种类型注入的方式 但以成果的信心 和可靠性来说 非常值得 正如我所说的 SharingProvider 在范例应用程序上 包含了不少重要的方法 我鼓励你查看它们的实现 以及我编写的测试 用来了解它们如何影响用户界面 总之 我添加了1200多行的测试代码 我希望这些例子能让它变得非常容易 帮助你在自己的应用中构建测试 我今天要讨论的最后一个主题是 对CloudKit中另一个新功能的支持 加密的CKRecord值 这些值是储存在 CKRecord上一个名为 encryptedValues的新负载中 而这在《CloudKit新增功能》 座谈中也有介绍过 CKRecord上的这个新负载 允许使用来自用户钥匙串 的密钥材料加密值 这些值在从CloudKit服务器下载后 在设备上本地解密 并且它们在设备上本地加密 之后才上传到CloudKit服务器 我们只须在Xcode点击一下 即可开启加密值采用 让我们看看它的实际效果
在Xcode中 我打开了我们的范例应用 将核心数据存储与云同步 我会打开CoreDataCloudKitDemo 托管对象模型 并且帖子实体上 有一个我想告诉你的 被称之为“位置”的特定属性 如果我选择位置特性 我可以在右侧数据模型检查器中 看到它的配置 我已将其配置为 “可随意转换的”特性 并选中这个 新的“允许云加密”复选框 这个新的复选框告诉 NSPersistentCloudKitContainer 这个属性的值 应存储在生成的CKRecord的 encryptedValues负载中 如果你更喜欢将矩阵当作代码读取 这里有个新的布尔值: allowsCloudEncryption 就在 NSAttributeDescription上 你可以使用它在你的模型代码中 来配置此属性 现在 使用CloudKit加密 是只有在一开始才能做的决定 这意味着我们以后不能改变主意 并选择今天在生产中 尚未加密的加密字段 同样的 你也不能选择 已经加密过的解密字段 一旦CloudKit架构被推送到生产中 我们就无法更改任何字段类型 所以一定要使用 NSPersistentCloudKitContainer的 initializeSchema方法 确保你的所有字段都存在 且在你将架构 部署到生产之前 类型是正确的
我很荣幸能介绍 我们所做的一些更改 为NSPersistentCloudKitContainer 带来了共享的支持 这里有很多你需要了解的新API 所以我们更新了我们的范例 应用程序和文档 来演示如何在自己的 应用程序中使用它 包括如何编写测试来验证 你的应用程序如何响应对象 在使用CloudKit时 可能经历的不同状态 如果你遇到任何问题 请务必 通过使用反馈助手 提交错误来告诉我们 一如往常 我等不及想看你们能使用 NSPersistentCloudKitContainer 构建出什么 保持活力 填满你的Apple Watch 圆环 好好享受WWDC 2021 [音乐]
-
-
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) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。