为啥CKModifyRecordsOperation批量删除CloudKit中的记录不删除记录?
Posted
技术标签:
【中文标题】为啥CKModifyRecordsOperation批量删除CloudKit中的记录不删除记录?【英文标题】:Why is CKModifyRecordsOperation to batch delete records in CloudKit not deleting records?为什么CKModifyRecordsOperation批量删除CloudKit中的记录不删除记录? 【发布时间】:2020-04-11 19:17:07 【问题描述】:我有代码可以从 CloudKit 的私有数据库中批量删除记录,但它不起作用。 (我还注意到我保存的记录没有保留。)我正在使用带有我个人使用的实际 Apple ID 的实际 iPhone 8,而不是开发人员帐户。我使用 iPhone 8 模拟器得到了相同的结果。当我使用 iPhone 8 Simulator 使用与我的开发者帐户相同的 Apple ID 登录 iCloud 时,我没有遇到此问题。
在这篇文章的答案没有给我解决方案之后,我认为这个问题最重要的是当我使用不是我的开发者帐户的 Apple ID 时会发生。我在两个不是我的开发者帐户的不同 Apple ID 上进行了尝试。会不会是我在某处俯瞰的设置?
*** 上的类似帖子都没有解决这个问题。
看起来代码应该删除的一些记录实际上被删除了,但有些没有。当我再次运行代码时,仍然有记录,但比以前少了一个。
这是我的代码:
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)
【问题讨论】:
【参考方案1】:有时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)
【讨论】:
没有一个叫做 CompletionHandler 的类型。该闭包的声明是什么? 哦,对不起,这是我喜欢使用的类型别名。它是一个以 SwiftsResult
值作为参数的闭包。 typealias CompletionHandler<T> = (Result<T, Error>) -> Void
它没有用。重新启动应用程序时,我仍有剩余记录。记录的计数少于先前的计数。一条或几条记录似乎已被永久删除。
没有。没有任何。我通过在 if let error = error 语句循环内部放置一个 print(error) 语句来检查。现在记录的数量甚至更低。似乎比以往更多的记录被永久删除。
我又试了一次,果然奏效了。所有记录都被永久删除。我不知道什么有效。谢谢。【参考方案2】:
尝试这些更改;
之后
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)
【讨论】:
尝试添加 operation.isAtomic = true 这不是必需的,但您可能会收到有用的错误消息。 没有。那没有做到。我还尝试了一次删除一个的代码。我用该信息更新了帖子。 如果我不是使用我的开发者帐户来测试 CloudKit,而是使用我个人使用的帐户,这有关系吗? 请注意,第二个“printNumberOfRecords”不是一个有效的调用,因为调用它时删除操作不会完成。在调用“printNumberOfRecords”之前,您必须等到所有删除调用都完成了它们的完成块。 是的。我注意到打印语句在调试输出的早期执行。【参考方案3】:我认为代码正在删除它从执行查询方法中获得的内容。只是 perform 查询方法没有从所有现有记录中返回记录 ID。
【讨论】:
以上是关于为啥CKModifyRecordsOperation批量删除CloudKit中的记录不删除记录?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?