iosswiftxcodecloudkitckmodifyrecordsoperation

Why is CKModifyRecordsOperation to batch delete records in CloudKit not deleting records?


I have code to batch delete records from the private database in CloudKit, but it's not working. (I also noticed that the records I saved are not remaining.) I am using an actual iPhone 8 with an actual Apple ID I personally use, not a developer account. I get the same results with an iPhone 8 Simulator. I don't have this problem when I log in to iCloud with the same Apple ID as my developer account using iPhone 8 Simulator.

After answers to this post have not given me a solution, I think the most important thing about this problem is that it occurs when I use an Apple ID that is not my developer account. I tried it on two different Apple IDs that are not my developer account. Could it be a setting I'm overlooking somewhere?

None of the similar posts on stackoverflow solves this problem.

It looks like some of the records that the code is supposed to delete is actually deleted, but some are not. When I run the code again, there exists records still, but about one less the number as before.

Here is my code:

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)

            }

        }

    }

}

Here is the output in the debug window from the first run of the code:

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

Here is the output in the debug window from the second run of the code:

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

When I use this code instead of 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()

                }

            }

        }

    }

}

I get in the debug window:

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__>)

Here is my latest code that still doesn't work:

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)
    }
}

Solution

  • I think the code was deleting what it got from the perform query method. It's just that the perform query method was not returning the record ids from all the existing records.