如何在创build新logging时使用CloudKit在没有延迟的情况下更新TableView中的数据
在我的应用程序中有2个视图控制器。
第一个“MainViewController”显示了从私有CloudKit数据库中获取的CKRecords字段的tableView。 在VC的viewWillAppear方法中,我从CloudKit获取logging并重新加载tableview的数据,以显示用户以前保存在CloudKit中的最新获取结果。
第二个视图控制器“CreateRecordViewController”用于创buildCKRecords并将其保存到CloudKit的专用数据库。
所以我在CreateRecordViewController中创buildlogging并在MainViewController中显示它们。
问题是:当我在CreateRecordViewController创buildlogging时,它保存在CloudKit服务器上,但closures此CreateRecordViewController并转到MainViewController后,tableview并不总是及时更新。
这是我CreateRecordViewController中保存logging的代码:
CloudKitManager.sharedInstance.privateDatabase.save(myRecord) { (savedRecord, error) -> Void in if error == nil { print("successfully saved record code: \(savedRecord)") } else { // Insert error handling print("error Saving Data to iCloud: \(error.debugDescription)") } }
logging保存后,我closuresCreateRecordViewController并查看MainViewController。
正如我之前在viewWillAppear of MainViewController中所说的,我检查iCloud是否可用,如果可用,我使用CloudKit中的查询获取所有logging,并在tableView中显示它们。
override func viewWillAppear(_ animated: Bool) { CKContainer.default().accountStatus { (accountStatus, error) in switch accountStatus { case .noAccount: print("CloudKitManager: no iCloud Alert") case .available: print("CloudKitManager: checkAccountStatus : iCloud Available") self.loadRecordsFromiCloud() case .restricted: print("CloudKitManager: checkAccountStatus : iCloud restricted") case .couldNotDetermine: print("CloudKitManager: checkAccountStatus : Unable to determine iCloud status") } } }
在loadRecordsFromiCloud()我也重新加载tableview async查询成功,以显示最新的结果。
位于我的MainViewController中的loadRecordsFromiCloud方法如下所示:
func loadRecordsFromiCloud() { // Get a private Database let privateDatabase = CloudKitManager.sharedInstance.privateDatabase let predicate = NSPredicate(value: true) let query = CKQuery(recordType: "MyRecords", predicate: predicate) let operation = CKQueryOperation(query: query) privateDatabase.perform(query, inZoneWith: nil) { (results, error) in if ((error) != nil) { // Error handling for failed fetch from public database print("error loading : \(error)") } else { // Display the fetched records //print(results!) self.tableViewDataArray = results! DispatchQueue.main.async { print("DispatchQueue.main.sync") self.tableView.reloadData() } } } }
有时,当CloudKit服务器工作得更快时,我可以在tableView中看到新logging,但大多数情况下会出现延迟(我没有在MainViewController加载的时候在tableview中看到新的结果),我认为这是因为当我抓取logging提取旧数据(不知道为什么),但也许我犯了另一个错误。 这是一个不好的用户体验,我想知道如何避免这种延迟。 我希望我的tableView在我closuresCreateRecordViewController后显示更新的结果。
我的第一个想法是订阅CloudKitlogging的变化 ,并在收到通知时获取和重新加载tableView中的数据,但我真的不需要推送通知(我宁愿有一个方法在代码中,我可以从中获取数据CloudKit之后,我知道所有的CloudKitlogging保存或之后,我知道有一个新的logging创build,并在获取和获取tableView的数据后,我会调用tableView.reloadData例如),但我不知道如何实现这个权利(在什么方法),不知道这是最好的解决scheme。 我也听说在WWDC 2016专用于CloudKit的video中,现在有一些与订阅logging更改相关的新方法,也许其中一些方法可以帮助(不确定)。 寻找最好的,或者这个问题的任何好的和简单的解决scheme(延迟问题)。
我正在使用XCode 8,iOS 10,Swift 3
无法保证logging在查询中何时可用,但是您可以执行某些操作。 您可以将新logging缝合回去。因为当您创build并保存logging时,您可以创build一个ckfetchrecord操作,并将新logging的ID传递给您,并保证立即将其恢复。 索引有时可能需要一段时间,这是令人沮丧的CloudKit。 所以基本上保证快速数据库的最好方法是进行查询,如果新的loggingID不在那里,使用id进行提取并将其追加到结果中。 希望这是有道理的。
之前我不得不这样做,因为我不太热衷于CK。 这里是链接到logging的操作。https://developer.apple.com/reference/cloudkit/ckfetchrecordsoperation如果你正在使用的图像检查出这个库,我作出,允许你排除图像数据键并根据需求进行下载和caching,以加快查询速度。 https://github.com/agibson73/AGCKImage
评论后编辑:
我认为你没有得到的部分是logging可能会或可能不会因查询控制器1中的查询而下降,因为索引的工作方式。 你甚至会提到你的问题,它提取旧数据。 这是由于服务器索引。 如果您删除了一条logging,也会发生这种情况。 它在查询中仍可能显示一段时间。 在这种情况下,您会跟踪最近删除的loggingID并在查询后将其删除。 再次,我所说的手动添加和删除是唯一的方法来保证用户看到的和查询的结果保持与用户期望的同步。
这里是一些代码,虽然完全没有经过testing,我希望能帮助你想象我上面说的。
func loadRecordsFromiCloud() { // Get a private Database let privateDatabase = CKContainer.default().privateCloudDatabase let predicate = NSPredicate(value: true) let query = CKQuery(recordType: "MyRecords", predicate: predicate) privateDatabase.perform(query, inZoneWith: nil) { (results, error) in if ((error) != nil) { // Error handling for failed fetch from public database print("error loading : \(error)") } else { //check for a newRecord ID that might be missing from viewcontroller 2 that was passed back if self.passedBackNewRecordID != nil{ let newResults = results?.filter({$0.recordID == self.passedBackNewRecordID}) //only excute if there is a new record that is missing from the query if newResults?.count == 0{ //houston there is a problem let additionalOperation = CKFetchRecordsOperation(recordIDs: [self.passedBackNewRecordID!]) additionalOperation.fetchRecordsCompletionBlock = { recordsDict,fetchError in if let newRecords = recordsDict?.values as? [CKRecord]{ //stitch the missing record back in let final = newRecords.flatMap({$0}) + results!.flatMap({$0}) self.reloadWithResults(results: final) self.passedBackNewRecordID = nil }else{ self.reloadWithResults(results: results) self.passedBackNewRecordID = nil } } privateDatabase.add(additionalOperation) }else{ //the new record is already in the query result self.reloadWithResults(results: results) self.passedBackNewRecordID = nil } }else{ //no new records missing to do additional check on self.reloadWithResults(results: results) } } } } func reloadWithResults(results:[CKRecord]?){ self.tableViewDataArray = results! DispatchQueue.main.async { print("DispatchQueue.main.sync") self.tableView.reloadData() } } }
这是一个混乱,但你可以看到,我正在拼接缺lessrecordID,如果没有零回到你正在做的查询,因为该查询是不能保证实时给你预期的新logging。 在这种情况下,self.passedBackNewRecordID基于Viewcontroller 2中的新recordID进行设置。如何设置此variables或跟踪此variables取决于您,但是您可能需要整个队列系统,因为我所告诉您应用于logging的更改为以及删除。 所以在一个制作应用程序中,我必须跟踪已经更改,删除和添加的logging,并获取每个这些logging的新版本,以便您可以想象对象列表的复杂性。 由于我停止使用CloudKit,因为逻辑删除或索引耗时太长,无法显示查询中的更改。
testing你保存的代码可能看起来像这样。
CloudKitManager.sharedInstance.privateDatabase.save(myRecord) { (savedRecord, error) -> Void in if error == nil { print("successfully saved record code: \(savedRecord)") //save temporarily to defaults let recordID = "someID" UserDefaults.standard.set(recordID, forKey: "recentlySaved") UserDefaults.standard.synchronize() //now we can dismiss } else { // Insert error handling print("error Saving Data to iCloud: \(error.debugDescription)") } }
而在视图控制器1中调用查询的代码可能viewWillAppear你可以调用这个
func startQuery(){ UserDefaults.standard.synchronize() if let savedID = UserDefaults.standard.value(forKey: "recentlySaved") as? String{ passedBackNewRecordID = CKRecordID(recordName: savedID) //now we can remove from Userdefualts UserDefaults.standard.removeObject(forKey: "recentlySaved") UserDefaults.standard.synchronize() } self.loadRecordsFromiCloud() }
这应该非常适合你的例子,并允许你testing我所说的只有很小的变化可能。