CloudKit 错误处理 - 重试逻辑

CloudKit error handling - retry logic

我想在我的应用程序中加入出色的 CloudKit 错误处理功能,正如 Apple 希望我们做的那样。我想立即保存和修改一条记录。这是我的基本保存逻辑...

func addNewRecord(managedObj: NSManagedObject) {
  let newRec = managedObj.convertToCkRecord()
  publicDB.saveRecord(newRec, completionHandler: saveHandler)
}

func saveHandler(savedRecord: CKRecord?, error: NSError?) {
  // handle errors here
  if let error = error {

    if error.code == CKErrorCode.NotAuthenticated.rawValue {
      // debug
      print("Not authentricated")
    }
    else if error.code == CKErrorCode.NetworkFailure.rawValue {
      print("Network failure!!")
      if let retryAfterValue = error.userInfo[CKErrorRetryAfterKey] as? NSTimeInterval {

        let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(retryAfterValue * Double(NSEC_PER_SEC)))
        dispatch_after(delayTime, dispatch_get_main_queue()) {
          // THIS IS WHERE I GET STUCK, WHERE DO I FIND THE FAILED CKRECORD FOR RETRY?  
          // IS IT IN USERINFO SOMEWHERE?
          //self.saveHandler(savedRecord, error: error)
        }
      }
    }
  }
  else {
    print("Save was a success! \(savedRecord)")
  }

}

这似乎应该是基本的,但我看到的每个例子都有评论 //retry//handle error,包括错误处理应该去的 WWDC 教程。我想知道的是如何找到对失败的 CKRecord 的引用?由于多线程问题,将它存储在局部变量中似乎行不通。我尝试将它添加到队列中,但其他线程也可以进入该队列,所以我担心竞争条件。

不使用 NSTimer,而是使用 dispatch_after

print("Network failure!!")
if let retryAfterValue = error.userInfo[CKErrorRetryAfterKey] as? NSTimeInterval {
    let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(retryAfterValue * Double(NSEC_PER_SEC)))
    dispatch_after(delayTime, dispatch_get_main_queue()) {
        saveHandler(saveRecord: saveRecord, error: error)
    }
}

这是我用于所有记录修改和删除的辅助方法(在Objective-C中)。它处理常见错误和重试。

- (void)modifyRecords:(NSArray<CKRecord *> *)records andDeleteRecords:(NSArray<CKRecordID *> *)deleteIds completion:(void (^)(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error))completion {
    CKModifyRecordsOperation *op = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:records recordIDsToDelete:deleteIds];
    op.savePolicy = CKRecordSaveAllKeys;
    op.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *operationError) {
        NSError *returnError = operationError;
        if (operationError) {
            switch (operationError.code) {
                case CKErrorRequestRateLimited:
                case CKErrorServiceUnavailable:
                case CKErrorZoneBusy:
                {
                    double delay = 3.0;
                    NSNumber *delayVal = operationError.userInfo[CKErrorRetryAfterKey];
                    if (delayVal) {
                        delay = delayVal.doubleValue;
                    }

                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
                        [self modifyRecords:records andDeleteRecords:deleteIds completion:completion];
                    });
                }
                    return;
                case CKErrorPartialFailure:
                {
                    if (savedRecords.count || deletedRecordIDs.count) {
                        returnError = nil;
                    }
                    break;
                }
                default:
                {
                    NSLog(@"Unhandled error in modify/deleteRecords: %@", operationError);
                }
                    break;
            }
        }

        if (completion) {
            completion(savedRecords, deletedRecordIDs, returnError);
        }
    };

    [someCloudKitDatabase addOperation:op];
}

如果您只想 add/modify 一条记录,请将 nil 传递给 deleteIds 参数。

这是 Swift 3 中的相同辅助方法(尚未测试,但除最后一行外它确实可以编译)。

func modifyRecords(_ records: [CKRecord]?, andDelete deleteIds: [CKRecordID]?, completionHandler: @escaping ([CKRecord]?, [CKRecordID]?, Error?) -> Void) {
    let op = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: deleteIds)
    op.savePolicy = .allKeys
    op.modifyRecordsCompletionBlock = { (_ savedRecords: [CKRecord]?, _ deletedRecordIds: [CKRecordID]?, _ operationError: Error?) -> Void in
        var returnError = operationError
        if let ckerror = operationError as? CKError {
            switch ckerror {
            case CKError.requestRateLimited, CKError.serviceUnavailable, CKError.zoneBusy:
                let retry = ckerror.retryAfterSeconds ?? 3.0
                DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retry, execute: {
                    modifyRecords(records, andDelete: deleteIds, completionHandler: completionHandler)
                })

                return
            case CKError.partialFailure:
                if (savedRecords != nil && savedRecords!.count > 0) || (deletedRecordIds != nil && deletedRecordIds!.count > 0) {
                    returnError = nil
                }
            default:
                break
            }
        }

        completionHandler(savedRecords, deletedRecordIds, returnError)
    }

    someCloudKitDatabase.add(op)
}