问题
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)
}
}
回答1:
Sometimes CloudKit will throw different errors at you, so you have to make sure you handle them, and trigger the call again if error contains retryAfterSeconds
. Here, use this wrapper method for CKDatabase
to handle the errors in the with very little effort. When calling any CloudKit API make sure to batch your requests to smaller chunks (ex. 100 items each).
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)
}
}
回答2:
Try these changes;
after
let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDsToDelete)
add
operation.database = privateDatabase
operation.queuePriority = .veryHigh
operation.configuration = CKOperation.Configuration()
operation.configuration.qualityOfService = .userInteractive
Then start the operation with;
operation.start()
instead of;
privateDatabase.add(operation)
回答3:
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.
来源:https://stackoverflow.com/questions/61162395/why-is-ckmodifyrecordsoperation-to-batch-delete-records-in-cloudkit-not-deleting