【问题标题】:CloudKit: Preventing Duplicate RecordsCloudKit:防止重复记录
【发布时间】:2026-02-16 15:10:01
【问题描述】:

我正在开发一个应用程序,该应用程序将数据从外部 Web 服务提取到私有 CloudKit 数据库中。该应用程序是单用户应用程序,但是我遇到了我不知道如何避免的竞争条件。

我的外部数据中的每条记录都有一个唯一标识符,我将其映射到我的 CKRecord 实例。一般的应用启动流程是:

  1. 获取相关记录类型的当前 CKRecords。
  2. 获取外部记录。
  3. 对于每个外部记录,如果CloudKit中不存在,则通过批量创建(修改操作)创建。

现在的问题是,如果这个过程同时在用户的两个设备上启动,因为 CK 和外部提取都是异步的,我很可能会得到重复的记录。

我知道我可以使用区域以原子方式提交我的所有 CKRecord 实例,但我认为这不能解决我的问题,因为如果所有这些提取基本上同时发生,那么保存就不是真正的问题。

我的问题是:

  1. 是否有人知道“锁定”私有数据库以在所有用户设备上进行写入的方法?
  2. 或者,有没有办法对任何 CKRecord 字段强制执行唯一性?
  3. 或者,有没有办法使用自定义值作为主键,在这种情况下,我可以使用我的外部 ID 作为 CK ID,并允许系统自行防止重复。

提前感谢您的帮助!

【问题讨论】:

  • 所以看起来我实际上可以将自定义名称传递给我自己的自定义 CKRecordID,谁能告诉我这是否是正确的方法?
  • 是的,您可以添加自己的 CKRecordID。然后它必须在整个容器中是唯一的(也可以在多个记录类型中)

标签: ios objective-c icloud cloudkit


【解决方案1】:

答案:

  1. 不,您不能锁定私有数据库
  2. Cloudkit 已经强制并假定您的记录 ID 具有唯一性
  3. 您可以随意设置记录 ID(在它的非区域部分)。

说明:

关于您的重复问题。如果您是创建记录 ID 的人(例如,来自您提到的外部记录),那么在最坏的情况下,如果您有竞争条件,您应该让一条记录用相同的数据覆盖另一条记录。我认为这不是极端情况下两个设备同时启动此过程的问题。基本上,您首先获取现有记录然后修改它们的逻辑对我来说似乎是合理的。

代码:

//employeeID is a unique ID to identify an employee
let employeeID = "001"

//Remember the recordID needs to be unique within the same database.
//Assuming you have different record types, it is better to prefix the record name with the record type so that it is unique
let recordName = "Employee-\(employeeID)"

//If you are using a custom zone
let customZoneID = CKRecordZoneID(zoneName: "SomeCustomZone", ownerName: CKCurrentUserDefaultName)
let recordIDInCustomZone = CKRecordID(recordName: recordName, zoneID: customZoneID)

//If you are using the default zone
let recordIDInDefaultZone = CKRecordID(recordName: recordName)

【讨论】:

    【解决方案2】:

    当我尝试读取包含 100 多条记录的数据库时,我遇到了类似的下载重复问题;该解决方案可在 Apple 的 Atlas 示例中找到,该示例使用布尔值来检查上一个进程是否在启动下一个进程之前完成。你会发现一个很像这样的块......

    @synchronized (self)
        {
            // Quickly returns if another loadNextBatch is running or we have the oldest post
            if(self.isLoadingBatch || self.haveOldestPost) return;
            else self.isLoadingBatch = YES;
        }
    

    顺便说一下,这里是创建您自己的记录密钥的代码。

    CKRecordID *customID = [[CKRecordID alloc] initWithRecordName:    [globalEOConfirmed returnEOKey:i]];
        newrecord = [[CKRecord alloc] initWithRecordType:@"Blah" recordID:customID];
    

    【讨论】:

    • 当代码在同一用户的 2 个不同设备上同时运行时,这是不可能的。正确的解决方案是使用自定义CKRecordID。根据唯一字段构建CKRecordID
    • 感谢您的反馈。您能否分享一些代码以及正确解决方案的示例?
    • 使用一致的方式创建CKRecordID(如您在答案中所示),因此当两台设备运行相同的代码来更新云记录时,它们将更新相同的记录创建单独的记录。 @synchronized 不提供额外的安全性,因为它们可能是 2 个完全不同的设备,唯一的 CKRecordID 就足够了。
    • 编辑答案以添加示例,希望对您有所帮助