Почему CKModifyRecordsOperation для пакетного удаления записей в CloudKit не удаляет записи? - PullRequest
2 голосов
/ 11 апреля 2020

У меня есть код для пакетного удаления записей из частной базы данных в CloudKit, но он не работает. (Я также заметил, что сохраненные мной записи не сохранились.) Я использую фактический iPhone 8 с фактическим Apple ID, который я использую лично, а не учетную запись разработчика. Я получаю те же результаты с симулятором iPhone 8. У меня нет этой проблемы, когда я захожу в iCloud с тем же Apple ID, что и моя учетная запись разработчика, используя iPhone 8 Simulator.

После того, как ответы на этот пост не дали мне решения, я думаю, что самое важное в этой проблеме - это то, что она возникает, когда я использую 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 ]

2 голосов
/ 14 апреля 2020

Иногда CloudKit выдаст вам разные ошибки, поэтому вам нужно убедиться, что вы их обработали, и снова вызвать вызов, если ошибка содержит retryAfterSeconds. Здесь используйте этот метод-обёртку для CKDatabase, чтобы обработать ошибки в нем с минимальными усилиями. При вызове любого API CloudKit убедитесь, что ваши запросы группируются по более мелким частям (например, по 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 голос
/ 13 апреля 2020

Попробуйте эти изменения;

после

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

add

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

Затем начните операцию с;

  operation.start()    

вместо ;

  privateDatabase.add(operation)
0 голосов
/ 23 апреля 2020

Я думаю, что код удалял то, что он получил от метода выполнения запроса. Просто метод выполнения запроса не возвращал идентификаторы записей из всех существующих записей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...