Comment mettre à jour des données dans TableView sans le timeout en utilisant CloudKit lors de la création de nouveaux loggings

Il y a 2 controllers de vue dans mon application.

Le premier "MainViewController" affiche une tableView avec les champs CKRecords récupérés à partir de la database privée CloudKit. À l'intérieur de la méthode viewWillAppear de ce VC, je récupère les loggings à partir de CloudKit et recharge datatables d'un tableau pour afficher les derniers résultats récupérés qui ont été précédemment enregistrés dans le CloudKit par l'user.

Le deuxième controller de vue "CreateRecordViewController" est créé pour créer CKRecords et les save dans la database privée de CloudKit.

Je crée donc des loggings dans CreateRecordViewController et les affiche dans MainViewController.

Le problème est le suivant: lorsque je crée un logging à CreateRecordViewController, il enregistre sur le server CloudKit, mais après la fermeture de CreateRecordViewController et en passant à MainViewController le tableau ne se met pas toujours à jour dans le time.

Voici le code d'logging de mon CreateRecordViewController:

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

Après l'logging enregistré, je ferme CreateRecordViewController et voir MainViewController.

Comme je l'ai dit plus haut dans viewWillAppear de MainViewController je vérifie si iCloud est disponible, et si elle est disponible, je récupère tous les loggings avec une requête de CloudKit et les montre dans une 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 .ressortingcted: print("CloudKitManager: checkAccountStatus : iCloud ressortingcted") case .couldNotDetermine: print("CloudKitManager: checkAccountStatus : Unable to determine iCloud status") } } } 

Dans loadRecordsFromiCloud (), je recharge également asview de tableview lorsque la requête a réussi à afficher les derniers résultats.

Ma méthode loadRecordsFromiCloud située dans mon MainViewController ressemble à ceci:

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

Parfois, lorsque les servers CloudKit fonctionnent plus rapidement, je peux voir les nouveaux loggings dans une tableView, mais la plupart du time il y a un timeout (je ne vois pas les nouveaux résultats dans une tableview au moment où MainViewController se charge). Je récupère les loggings, il récupère les anciennes données (je ne sais pas pourquoi), mais peut-être que j'ai fait une autre erreur. C'est une mauvaise expérience user et je voudrais savoir comment éviter ce timeout. Je veux que mon tableView affiche les résultats mis à jour juste après que je ferme CreateRecordViewController.

Ma première idée était de m'abonner aux modifications de CloudKit Records , et de récupérer et recharger datatables dans un tableauView lorsque la notification est reçue, mais je n'ai pas vraiment besoin d'une notification push (je préfèrerais simplement avoir une méthode dans le code CloudKit après que je sais que tous les loggings CloudKit enregistrés ou après que je sais qu'il y a un nouvel logging créé, et après avoir récupéré et obtenu datatables pour un tableau, j'appellerais tableView.reloadData par exemple), mais je ne suis pas sûr de savoir comment implémenter ce droit (dans quelle méthode) et je ne sais pas si c'est la meilleure solution. J'ai aussi entendu dire que dans la video WWDC 2016 dédiée à CloudKit, il y a maintenant de nouvelles methods liées à l'abonnement aux changements d'loggings, peut-être que certaines de ces methods peuvent aider (pas sûr). Vous cherchez le meilleur, ou n'importe quelle solution bonne et facile pour ce problème (problème de retard).

J'utilise XCode 8, iOS 10, swift 3

Il n'y a aucune garantie quant à quand l'logging serait disponible dans une question mais il y a quelque chose que vous pouvez faire. Vous pouvez recoller le nouvel logging. Parce que lorsque vous créez et enregistrez un logging, vous avez l'ID d'logging que vous pouvez faire une opération ckfetchrecords et transmettre l'ID du nouvel logging et vous êtes assuré de le récupérer immédiatement. L'indexing peut parfois prendre un certain time et c'est frustrant avec CloudKit. Donc, fondamentalement, la meilleure façon de garantir une database rapide est de faire une requête et si le nouvel identifiant d'logging n'est pas là, faites une extraction avec l'identifiant et ajoutez-la à vos résultats. J'espère que cela a du sens.

Je devais le faire avant et depuis que je n'ai pas été trop enthousiaste sur CK. Voici le lien vers l'opération pour recoller l'logging. https://developer.apple.com/reference/cloudkit/ckfetchrecordsoperation aussi si vous utilisez des images consultez cette bibliothèque que j'ai faite qui vous permet d'exclure les keys de données d'image et download et mettre en cache à la request qui pourrait accélérer vos requêtes. https://github.com/agibson73/AGCKImage

Modifier après commentaire:

Je pense que la partie que vous n'obtenez pas est l'logging peut ou ne peut pas descendre avec la requête dans viewcontroller 1 en raison de la façon dont l'indexing fonctionne. Vous mentionnez même dans votre question qu'il récupère des données anciennes. Cela est dû à l'indexing du server. La même chose se produirait si vous supprimiez un logging. Il pourrait encore apparaître pendant un certain time dans la requête. Dans ce cas, vous conservez les identifiants d'logging récemment supprimés et supprimez-les après la requête. Encore une fois, l'ajout et la suppression manuellement dont je parle sont la seule façon de garantir ce que les users voient et les résultats de la requête restnt en phase avec ce à quoi l'user s'attendrait.

Voici un code bien que complètement non testé que j'espère vous aidera à visualiser ce que je dis ci-dessus.

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

C'est un peu un gâchis mais vous pouvez voir que je couds l'ID d'logging manquant si pas nil dans la requête que vous faites parce que cette requête n'est pas garantie en time réel pour vous donner vos nouveaux loggings attendus. Dans ce cas, self.passedBackNewRecordID est défini en fonction du nouvel ID d'logging de Viewcontroller 2. La manière dont vous définissez ou suivez cette variable dépend de vous, mais vous avez probablement besoin d'un système de queue complet car ce que je vous dis request des modifications de l'logging. ainsi que supprime. Donc, dans une application de production, je devais suivre les loggings qui avaient des changements, des suppressions et des ajouts et get la nouvelle version de chacun de ceux-ci afin que vous puissiez imaginer la complexité d'une list d'objects. Depuis que j'ai arrêté d'utiliser CloudKit parce que la désactivation ou l'indexing prend trop de time pour montrer les changements dans les requêtes.

Pour tester votre code enregistré pourrait ressembler à ceci.

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

Et dans le code où vous appelez la requête dans le controller de vue 1 éventuellement viewWillAppear vous pouvez appeler cela

 func startQuery(){ UserDefaults.standard.synchronize() if let savedID = UserDefaults.standard.value(forKey: "recentlySaved") as? Ssortingng{ passedBackNewRecordID = CKRecordID(recordName: savedID) //now we can remove from Userdefualts UserDefaults.standard.removeObject(forKey: "recentlySaved") UserDefaults.standard.synchronize() } self.loadRecordsFromiCloud() } 

Cela devrait correspondre à votre exemple de près et vous permettre de tester ce que je dis avec seulement des changements mineurs éventuellement.