Question sur les méthodes async de CloudKit
Bonjour à tous,
Petite question architecturale.
Les méthodes de CloudKit sont asynchrones (Ce qui est normal) et elles possède un complétionHandler afin d'agir une fois le retour fait.
Dans mon cas j'ai une succession de perform de Query a faire et je m'interroge si je procède de la bonne manière. Je trouve mon code "Sale" (avec mes completionHandler imbriqués, et encore il n'y en a que deux pour l'instant).
Mon objectif est d'exécuter plusieurs perform consécutif de manière synchrones entre eux.
La méthode globale elle pourrait être asynchrone.
Voici mon code :
override func viewDidLoad() {
super.viewDidLoad()
self.api.getConnectedUserID { (userRecordID, nsError) in
if nsError != nil {
self.connexionLabel.stringValue = nsError!.localizedDescription
} else {
self.api.fetchUserInformations(withUserRecordID: userRecordID!, completionHandler: { (userInfo, nsError) in
if nsError != nil {
self.connexionLabel.stringValue = nsError!.localizedDescription
} else {
self.userInformations = userInfo
guard let userInfo = self.userInformations else {
return
}
DispatchQueue.main.async {
if let name = userInfo.name, let firstname = userInfo.firstname {
self.userTextField.stringValue = firstname + " " + name
}
if let note = userInfo.notes {
self.connexionLabel.stringValue = note
}
}
}
})}
}
}
//
// CloudKitAPI.swift
// iodaProject
//
// Created by Arnaud on 17-05-17.
// Copyright © 2017 iodarno. All rights reserved.
//
import Foundation
import CloudKit
class CloudKitAPI {
let publicDB = CKContainer.default().publicCloudDatabase
let privateDB = CKContainer.default().privateCloudDatabase
func getConnectedUserID(completionHandler: @escaping (CKRecordID?, NSError?) -> Void) {
CKContainer.default().fetchUserRecordID { (recordID, error) in
if error != nil {
let nsError = error! as NSError
print("Error 'getConnectedUserID' : \(nsError.code) | \(String(describing: nsError.localizedDescription))")
completionHandler(nil, nsError)
} else {
completionHandler(recordID, nil)
}
}
}
func fetchUserInformations(withUserRecordID recordID: CKRecordID, completionHandler: @escaping (UserInformations?, NSError?) -> Void) {
let reference = CKReference(recordID: recordID, action: .deleteSelf)
let predicate = NSPredicate(format: "User == %@", reference)
let query = CKQuery(recordType: "UsersInformations", predicate: predicate)
self.publicDB.perform(query, inZoneWith: nil) { (records, error) in
if error != nil {
let nsError = error! as NSError
print("Error 'fetchUserInformations.perform' : \(nsError.code) | \(String(describing: (error?.localizedDescription)!))")
completionHandler(nil, nsError)
} else {
guard let recs = records else {
print("Error 'fetchUserInformations.recordsCount' : Aucun résultat trouvés")
return
}
guard recs.count == 1 else {
print("Error 'fetchUserInformations.recordsCount' : Plusieurs résultats trouvés (\(recs.count))")
return
}
let userInformationRecord = recs.first!
let userInformations = UserInformations(withRecordID: userInformationRecord["recordID"] as! CKRecordID, name: userInformationRecord["Name"] as! String?, firstname: userInformationRecord["Firstname"] as! String?, notes: userInformationRecord["Note"] as! String?)
completionHandler(userInformations, nil)
}
}
}
}
Merci par avance de vos expertises.
Mots clés:
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Je ne suis pas sûr qu'on puisse écrire mieux " de base ".
Si ça t'intéresses, tu peux jeter un oe“il au concept des " Promises " (par ex. avec PromiseKit). ça reste relativement simple et répond directement à ton besoin.
Si tu n'as pas peur et veut une abstraction plus large, étudie la programmation Réactive avec RxSwift or ReactiveCocoa.
Je trouve l'idée des Promises tres intéressantes.
Je ne connaissais pas du tout.
En même temps je ne travaille pas souvent dans l'asynchrone.
Je vais implémenter ceci et voir ce que ça donne.
Bonne journée. Encore merci.
Après quelques galères pour utiliser CocoaPods, je m'en suis sorti.
Voici le résultat.
Tout de suite plus propre.
Pas sûr en effet que cela soit simplifiable.
Si tu souhaites ne pas utiliser Promises, tu peux peut-être faire ainsi :
Créer un handler de success et un d'erreur. Cela te permettra peut-être d'aller plus rapidement dans le cas où tout se passe bien.
Créer des méthodes qui sont plus invasives, dans le sens où tu peux créer des méthodes qui en appelleront une autre avec une closure, et ainsi les regrouper deux à deux, puis après peut-être en regrouper deux à deux entre elles, etc.
PromiseKit fonctionne exactement comme les promises de JavaScript.
Ça règle souvent beaucoup de soucis et c'est robuste.
Une autre méthode c'est d'utiliser des semaphores pour rendre l'asynchrone séquentiel et ainsi avoir un code moins imbriqué.
Moins élégant que les promises mais "tout est là ", pas de bibliothèque à gérer.
On peut également utiliser les DispatchGroup si on veut plus de parallélisme pour certaines étapes.