为什么要在CloudKit中批量删除记录的CKModifyRecordsOperation不删除记录?

时间:2020-04-11 19:17:07

标签: ios swift xcode cloudkit ckmodifyrecordsoperation

我有代码从CloudKit的私有数据库中批量删除记录,但是它不起作用。 (我还注意到,保存的记录不会保留。)我使用的是带有我个人使用的真实Apple ID的实际iPhone 8,而不是开发者帐户。使用iPhone 8模拟器,我得到相同的结果。使用iPhone 8 Simulator以与开发人员帐户相同的Apple ID登录到iCloud时,我没有此问题。

在对这篇文章的回答没有给出解决方案之后,我认为关于此问题的最重要的事情是,当我使用不是我的开发者帐户的Apple ID时,就会发生此问题。我在不是我的开发人员帐户的两个不同的Apple ID上进行了尝试。这可能是我在某处俯瞰的环境吗?

在stackoverflow上没有类似的帖子可以解决此问题。

看起来应该删除该代码的某些记录实际上已删除,但没有删除。当我再次运行代码时,仍然存在记录,但是记录数比以前少了。

这是我的代码:

class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let splitViewController = window!.rootViewController as! UISplitViewController
        let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
        navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
        splitViewController.delegate = self

        deleteRecords()
        return true
    }

}

let privateDatabase = CKContainer.default().privateCloudDatabase

func deleteRecords() {

    print("deleteRecords()")

    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: DatabaseNameStrings.recordTypeAffirmation, predicate: predicate)

    privateDatabase.perform(query, inZoneWith: nil) {

        (records: [CKRecord]?, error: Error?) in

        if error != nil {

            print(error as Any)

        } else {

            if let records = records {

                print("records.count=", records.count)

                let recordIDsToDelete = records.map { $0.recordID }
                print("recordIDsToDelete:")
                print(recordIDsToDelete)
                let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDsToDelete)
                operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
                    if error == nil {
                        print("Batch delete records!")
                        print("number of records deleted:", deletedRecordIDs?.count as Any)
                        printNumberOfRecords()
                    } else {
                        print(error as Any)
                    }
                }
                privateDatabase.add(operation)

            }

        }

    }
}

func printNumberOfRecords() {

    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: DatabaseNameStrings.recordTypeAffirmation, predicate: predicate)

    privateDatabase.perform(query, inZoneWith: nil) {

        (records: [CKRecord]?, error: Error?) in

        if error != nil {

            print(error as Any)

        } else {

            if let records = records {

                print("Number of records in CloudKit=", records.count)

            }

        }

    }

}

这是代码的第一次运行在调试窗口中的输出:

deleteRecords()
records.count= 93
recordIDsToDelete:
[<CKRecordID: 0x280bbcb00; recordName=B33A3F23-23D3-44C6-AEBC-86DD718DBB62, zoneID=...>, ... ]
Batch delete records!
number of records deleted: Optional(93)
Number of records in CloudKit= 67

这是第二次运行代码时在调试窗口中的输出:

deleteRecords()
records.count= 92
recordIDsToDelete:
[<CKRecordID: 0x280080d00; recordName=BBA5B236-A036-4AC9-82E1-165D3B003E23, zoneID=...>, ... ]
Batch delete records!
number of records deleted: Optional(92)
Number of records in CloudKit= 52

当我使用此代码而不是deleteRecords()时...

func deleteRecordsOneAtATime() {

    print("deleteRecordsOneAtATime()")

    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: DatabaseNameStrings.recordTypeAffirmation, predicate: predicate)

    privateDatabase.perform(query, inZoneWith: nil) {

        (records: [CKRecord]?, error: Error?) in

        if error != nil {

            print(error as Any)

        } else {

            if let records = records {

                print("records.count=", records.count)

                let recordIDsToDelete = records.map { $0.recordID }
                print("recordIDsToDelete:")
                print(recordIDsToDelete)

                for recordID in recordIDsToDelete {

                    privateDatabase.delete(withRecordID: recordID) {
                        (localRecordID: CKRecord.ID?, error: Error?) in
                        if error != nil {
                            print("error:\n", error as Any)
                        } else {
                            if localRecordID != nil {
                                print("localRecordID:", localRecordID as Any)
                            }
                        }
                    }

                printNumberOfRecords()

                }

            }

        }

    }

}

我进入调试窗口:

deleteRecordsOneAtATime()
Number of records in CloudKit= 97
records.count= 97
recordIDsToDelete:
[<CKRecordID: 0x283622ec0; recordName=600B7BFE-04FE-4F63-BC4C-5AD1AE08908D, zoneID=...>, ... ]
localRecordID: Optional(<CKRecordID: 0x2821ff320; recordName=8E8CD0F0-FDF5-4CB9-B16C-5CF91C3503A2, zoneID=_defaultZone:__defaultOwner__>)
localRecordID: Optional(<CKRecordID: 0x2821ff320; recordName=8E0A0816-1B05-4707-A4E7-C40762E68663, zoneID=_defaultZone:__defaultOwner__>)
localRecordID: Optional(<CKRecordID: 0x28210b200; recordName=8E127624-F1D3-401E-ADF2-BB97354FCA98, zoneID=_defaultZone:__defaultOwner__>)
...
Number of records in CloudKit= 87
localRecordID: Optional(<CKRecordID: 0x282108660; recordName=962639D1-83E6-40D2-A57D-F70ADCEBED08, zoneID=_defaultZone:__defaultOwner__>)
localRecordID: Optional(<CKRecordID: 0x28210ff20; recordName=968D62AB-523E-464B-94B8-3C90E0382AB6, zoneID=_defaultZone:__defaultOwner__>)
localRecordID: Optional(<CKRecordID: 0x28210faa0; recordName=96C92DD2-ED27-4FED-8320-44D03981B04F, zoneID=_defaultZone:__defaultOwner__>)
localRecordID: Optional(<CKRecordID: 0x2821085a0; recordName=96A2D515-D3E7-475E-B609-8389DE4B88D1, zoneID=_defaultZone:__defaultOwner__>)

这是我最新的代码仍然不起作用:

func removeRecords() {

    print("removeRecords()")

    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: DatabaseNameStrings.recordTypeAffirmation, predicate: predicate)

    privateDatabase.perform(query, inZoneWith: nil) {

        (records: [CKRecord]?, error: Error?) in

        if error != nil {

            print(error as Any)

        } else {

            if let records = records {

                print("records.count=", records.count)

                let recordIDsToDelete = records.map { $0.recordID }
                print("recordIDsToDelete:")
                print(recordIDsToDelete)

                DispatchQueue.main.async {

                    if recordIDsToDelete.count > 50 {
                        let slice = Array(recordIDsToDelete[0 ..< 50])
                        let leftOver = Array(recordIDsToDelete[50 ... recordIDsToDelete.count-1])

                        privateDatabase.remove(recordsWith: slice) {
                            (result: Result<Void, Error>) in
                            print("result:", result)
                            switch result {
                            case .failure(let err):
                                print("failure")
                                print(err)
                            case .success(()):
                                print("success")
                                DispatchQueue.main.async {
                                    privateDatabase.remove(recordsWith: leftOver) {
                                        (result: Result<Void, Error>) in
                                        print("result:", result)
                                        switch result {
                                        case .failure(let err):
                                            print("failure")
                                            print(err)
                                        case .success(()):
                                            print("success")
                                        }
                                        print("type of result:", type(of: result))
                                    }
                                }
                            }
                            print("type of result:", type(of: result))
                        }

                    } else {

                        privateDatabase.remove(recordsWith: recordIDsToDelete) {
                            (result: Result<Void, Error>) in
                            print("result:", result)
                            switch result {
                            case .failure(let err):
                                print("failure")
                                print(err)
                            case .success(()):
                                print("success")
                            }
                            print("type of result:", type(of: result))
                        }

                    }





                    privateDatabase.remove(recordsWith: recordIDsToDelete) {
                        (result: Result<Void, Error>) in
                        print("result:", result)
                        switch result {
                        case .failure(let err):
                            print("failure")
                            print(err)
                        case .success(()):
                            print("success")
                        }
                        print("type of result:", type(of: result))
                    }

                }

            }

        }

    }

}

extension CKDatabase {
    func remove(
        recordsWith ids: [CKRecord.ID], completion: @escaping CompletionHandler<Void>) {
        let operation = CKModifyRecordsOperation(recordIDsToDelete: ids)
        operation.qualityOfService = .userInitiated
        operation.modifyRecordsCompletionBlock = { _, _, error in
            if let error = error {
                print("error:")
                print(error)
                if let err = error as? CKError, let time = err.retryAfterSeconds {
                    DispatchQueue.main.asyncAfter(deadline: .now() + time) {
                        self.remove(recordsWith: ids, completion: completion)
                    }
                } else {
                    completion(.failure(error))
                    print("CKDatabase.remove(_:_:) failed.")
                }
            } else {
                completion(.success(()))
                print("CKDatabase.remove(_:_:) succeeded.")
            }
        }
        self.add(operation)
    }
}

3 个答案:

答案 0 :(得分:2)

有时CloudKit会向您抛出不同的错误,因此您必须确保处理它们,并在错误包含retryAfterSeconds时再次触发该调用。在这里,使用CKDatabase的包装方法可以非常轻松地处理中的错误。调用任何CloudKit API时,请确保将您的请求批量处理成较小的块(每个块100个项目)。

extension CKDatabase {
    func remove(
        recordsWith ids: [CKRecord.ID], 
        completion: @escaping (Result<Void, Error>) -> Void
    ) {
        let operation = CKModifyRecordsOperation(recordIDsToDelete: ids)
        operation.qualityOfService = .userInitiated
        operation.modifyRecordsCompletionBlock = { _, _, error in
            if let error = error {
                if let err = error as? CKError, let time = err.retryAfterSeconds {
                    DispatchQueue.main.asyncAfter(deadline: .now() + time) {
                        self.remove(recordsWith: ids, completion: completion)
                    }
                } else {
                    completion(.failure(error))
                }
            } else {
                completion(.success(()))
            }
        }
        self.add(operation)
    }
}

答案 1 :(得分:1)

尝试这些更改;

之后

  let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDsToDelete)

添加

  operation.database = privateDatabase
  operation.queuePriority = .veryHigh
  operation.configuration =  CKOperation.Configuration()
  operation.configuration.qualityOfService = .userInteractive

然后开始操作;

  operation.start()    

代替;

  privateDatabase.add(operation)

答案 2 :(得分:0)

我认为代码正在删除执行查询方法中的内容。只是perform查询方法没有从所有现有记录中返回记录ID。