Ios 将记录CKRecordID保存到服务器时出错:从类型';更新记录的尝试无效;X';至';Y';

Ios 将记录CKRecordID保存到服务器时出错:从类型';更新记录的尝试无效;X';至';Y';,ios,objective-c,cloudkit,ckmodifyrecordsoperation,ckerror,Ios,Objective C,Cloudkit,Ckmodifyrecordsoperation,Ckerror,当我使用CKModifyRecordsOperation将多个表的记录保存到私有云数据库的默认区域时,它总是返回以下错误,表“X”除外: 将记录保存到服务器时出错:将记录从类型“X”更新为“Y”的尝试无效 错误。userInfo详细信息: { CKErrorDescription=“将记录CKRecordID:0x7fd7a3d4c0c0;1:(\u defaultZone:defaultOwner)保存到服务器时出错:将记录从类型“X”更新为“Y”的尝试无效; ContainerID=“iCl

当我使用
CKModifyRecordsOperation
将多个表的记录保存到私有云数据库的默认区域时,它总是返回以下错误,表“X”除外:

将记录保存到服务器时出错:将记录从类型“X”更新为“Y”的尝试无效

错误。userInfo
详细信息:

{
CKErrorDescription=“将记录CKRecordID:0x7fd7a3d4c0c0;1:(\u defaultZone:defaultOwner)保存到服务器时出错:将记录从类型“X”更新为“Y”的尝试无效;
ContainerID=“iCloud.com…”;
NSDebugDescription=“CKInternalErrorDomain:2006”
NSLocalizedDescription=“将记录保存到服务器时出错:将记录从类型“X”更新为“Y”的尝试无效;”
NSUnderlyingError=“CKError 0x7fa0d250c4e0:\“无效参数\”(2006);服务器消息=\“将记录从类型“X”更新为“Y”的尝试无效”;uuid=E2E…D1E;容器ID=\“iCloud.com…\”;
RequestUUID=“E2E…D1E”;
ServerErrorDescription=“将记录从类型“X”更新为“Y”的尝试无效;”
errorKey=ck1rosofi;
}

相关代码段:

- (void)sync
{
  ...
  NSMutableArray * operations;
  for (NSString *tableName in @[@"X", @"Y"]) {
    CKModifyRecordsOperation * operation = [self _modifyRecordsOperationWithTableName:tableName];
    if (operation) {
      if (operations) [operations addObject:operation];
      else operations = [NSMutableArray arrayWithObject:operation];
    }
  }

  if (operations) {
    [operationQueue addOperations:operations waitUntilFinished:NO];
  }
}

- (CKModifyRecordsOperation *)_modifyRecordsOperationWithTableName:(NSString *)tableName
{
  ...

  NSMutableArray * recordsToSave = [NSMutableArray array];
  for (KYModel <KYModel_iCloudProtocol> *instance in unsyncedInstances) {
    CKRecordID * objectID = [[CKRecordID alloc] initWithRecordName:@(instance.id).stringValue];
    CKRecord * cloudRecord = [[CKRecord alloc] initWithRecordType:tableName recordID:objectID];
    ... setup record detail
    [recordsToSave addObject:record];
  }

  CKModifyRecordsOperation * operation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:nil];
  operation.database = [[CKContainer defaultContainer] privateCloudDatabase];
  operation.savePolicy = CKRecordSaveAllKeys;
  operation.qualityOfService = NSQualityOfServiceUserInteractive;
  operation.atomic = NO;
  operation.perRecordProgressBlock = ...;
  operation.perRecordCompletionBlock = ^(CKRecord *record, NSError *error) {
    if (error) {
      // got error here
    }
    ...
  };
  operation.modifyRecordsCompletionBlock = ...;

  return operation;
}
-(无效)同步
{
...
NSMutableArray*操作;
for(NSString*表格名在@[@“X”,“Y”]中){
CKModifyRecordsOperation*操作=[self _modifyRecordsOperationWithTableName:tableName];
if(操作){
if(操作)[操作添加对象:操作];
else operations=[NSMutableArray arrayWithObject:operation];
}
}
国际单项体育联合会(运作){
[操作队列添加操作:操作等待完成:否];
}
}
-(CKModifyRecordsOperation*)_modifyRecordsOperationWithTableName:(NSString*)tableName
{
...
NSMutableArray*recordsToSave=[NSMutableArray];
对于(非同步实例中的KYModel*实例){
CKRecordID*objectID=[[CKRecordID alloc]initWithRecordName:@(instance.id).stringValue];
CKRecord*cloudRecord=[[CKRecord alloc]initWithRecordType:tableName recordID:objectID];
…设置记录详细信息
[记录保存添加对象:记录];
}
CKModifyRecordsOperation*操作=[[CKModifyRecordsOperation alloc]initWithRecordsToSave:recordsToSave recordIDsToDelete:nil];
operation.database=[[CKContainer defaultContainer]privateCloudDatabase];
operation.savePolicy=ckrecordsaveallkey;
operation.qualityOfService=nsqualityofservice用户交互;
operation.atomic=NO;
operation.perRecordProgressBlock=。。。;
operation.perRecordCompletionBlock=^(CKRecord*record,NSError*error){
如果(错误){
//这里有错误
}
...
};
operation.modifyRecordsCompletionBlock=。。。;
返回操作;
}

在搜索和调试了一段时间后,我意识到那些失败的记录已经在同一个区域中,尽管它们属于不同的表,但它们具有相同的“记录名称”(这是
CKRecordID
的唯一名称,就像每个记录都有一个唯一的ID)


解决方案一: 使CKRecordID的“记录名”在所有表中唯一

例如,为每个记录名添加一个表名前缀:[table\u name]\uid]

上述案例的问题是,同一区域(默认区域)中不同表的记录的“记录名称”重复

使用
CKModifyRecordsOperation
ckRecordsSaveAllKeys
保存策略,当保存表Y的记录(id:1)时,它在云端找到表X的另一条记录(id:1),并尝试对其进行修改,最后抛出错误“将记录从类型“X”更新为“Y”的尝试无效”


解决方案二: 为每个表创建自定义区域(注意:自定义区域仅适用于私有云数据库)

例如,不使用默认区域,而是对表X使用自定义区域“X_Zone”,对表Y使用“Y_Zone”。然后我们可以继续使用@“id”作为CKRecordID的“记录名称”

这样,在将本地记录转换为CKRecord实例时,还需要提供
zoneID

CKRecordZoneID *zoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Custom_Zone_Name_Here"
                                                         ownerName:CKOwnerDefaultName];
CKRecordID *objectID = [[CKRecordID alloc] initWithRecordName:@(instance.id).stringValue zoneID:zoneID];
...
当然,您需要首先创建相关的自定义区域:

CKRecordZone *zone = [[CKRecordZone alloc] initWithZoneName:@"Custom_Zone_Name"];
[[[CKContainer defaultContainer] privateCloudDatabase] saveRecordZone:zone completionHandler:...];
或同时创建多个:

NSMutableArray *zones = [NSMutableArray array];
for (NSString *zoneName in @[...]) {
  CKRecordZone *zone = [[CKRecordZone alloc] initWithZoneName:zoneName];
  [zones addObject:zone];
}
CKModifyRecordZonesOperation * operation = [[CKModifyRecordZonesOperation alloc] initWithRecordZonesToSave:zones recordZoneIDsToDelete:nil];
...
[[[CKContainer defaultContainer] privateCloudDatabase] addOperation:operation];

我已经测试了这两种方法&效果如预期


建议: 如果您的记录存储在私有云数据库中,我认为解决方案二将是一个不错的选择,正如文档所说:

。。。使用自定义区域在专用数据库中排列和封装相关记录组。自定义区域还支持其他功能,例如将多个记录作为单个原子事务写入的功能

将每个自定义区域视为独立于数据库中每个其他区域的单个数据单元。在区域内,您可以像在其他任何地方一样添加记录。

处理每个表的记录会方便得多,比如删除表的区域,而不是查询和删除同一区域中表的记录

但请注意,如果在表中使用
CKReference
,则不要为这些表使用自定义区域,因为

。。。CKReference类不支持跨区域链接,因此每个引用对象必须指向与当前记录位于同一区域中的记录


顺便说一句,这是自我回答问题的答案,希望它能帮助其他人解决同样的问题。如果我的回答有错误,请指出,并欢迎任何建议,提前通知。)