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:

Réponses

  • CéroceCéroce Membre, Modérateur

    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.

  • Merci beaucoup Céroce.

    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.



    override func viewDidLoad() {
    super.viewDidLoad()

    self.api.getConnectedUserIDP().then { recordID in
    self.api.fetchUserInformationsP(withUserRecordID: recordID)
    }.then { userInfo in
    self.afficherInformation(withUserInformations: userInfo)
    }.catch { (error) in
    self.displayError(withNsError: error as NSError)
    }
    }


    func afficherInformation(withUserInformations userInformations: UserInformations?) -> Void {
    self.userInformations = userInformations
    if let userInfo = userInformations {
    if let name = userInfo.name, let firstname = userInfo.firstname {
    self.userTextField.stringValue = firstname + " " + name
    }
    if let note = userInfo.notes {
    self.connexionLabel.stringValue = note
    }
    }
    }

    Tout de suite plus propre.


  • LarmeLarme Membre

    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.


  • CéroceCéroce Membre, Modérateur

    Tout de suite plus propre.

    Merci de ton retour. Je n'ai encore jamais travaillé avec PromiseKit, mais il semble que tu as rapidement réussi à  en tirer quelque chose. Les échos que j'en avait était justement que c'était une solution relativement simple.
  • PyrohPyroh Membre

    PromiseKit fonctionne exactement comme les promises de JavaScript.


    Ça reÌ€gle souvent beaucoup de soucis et c'est robuste. 


  • FKDEVFKDEV Membre
    mai 2017 modifié #8

    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.




    DispatchQueue.global().async {
    var resultStep1:ResultObject? = nil
    let semaphore = DispatchSemaphore(value: 0)
    AnObject.DoSomethingAsync() {
    resultStep1 = AsyncStuffIsFinished();
    semaphore.signal
    }

    //Ici on attend avant de continuer (ne pas le faire dans le thread principal)
    semaphore.wait(timeout:DispatchTime.distantFuture)


    let semaphoreStep2 = DispatchSemaphore(value: 0)
    AnotherObject.DoSomethingAsync(resultStep1) {
    semaphoreStep2.signal
    }
    semaphoreStep2.wait(timeout:DispatchTime.distantFuture)
    }


    On peut également utiliser les DispatchGroup si on veut plus de parallélisme pour certaines étapes.


Connectez-vous ou Inscrivez-vous pour répondre.