Aller au contenu


Photo

Question sur les méthodes async de CloudKit

CloudKit

  • Please log in to reply
7 réponses à ce sujet

#1 Yodarno

Yodarno

    Eleveur de cacaoyers

  • Membre
  • PipPip
  • 14 messages
  • LocationCanada

Posté 18 mai 2017 - 11:23

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.


  • colas_ aime ceci

Que la pomme soit avec vous !


#2 Céroce

Céroce

    Mouleur de chocolats

  • Contrôleur d'arômes
  • 5 325 messages
  • LocationSaint-Leu-d'Esserent / France

Posté 18 mai 2017 - 11:30

Je ne suis pas sûr qu'on puisse écrire mieux « de base ».

 

Si ça t'intéresses, tu peux jeter un œ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.


  • colas_ aime ceci
RenaudPradenc.com Je suis développeur iOS & Mac indépendant.

#3 Yodarno

Yodarno

    Eleveur de cacaoyers

  • Membre
  • PipPip
  • 14 messages
  • LocationCanada

Posté 18 mai 2017 - 12:06

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.

Que la pomme soit avec vous !


#4 Yodarno

Yodarno

    Eleveur de cacaoyers

  • Membre
  • PipPip
  • 14 messages
  • LocationCanada

Posté 18 mai 2017 - 21:05

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.


  • iLandes et colas_ aiment ceci

Que la pomme soit avec vous !


#5 Larme

Larme

    Broyeur de fèves

  • Artisan chocolatier
  • PipPipPipPipPipPip
  • 1 981 messages
  • LocationParis

Posté 18 mai 2017 - 21:18

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.


Tant que vous avez des dents, mangez des pommes. Tant que vous avez de l'argent, croquez la Pomme.

#6 Céroce

Céroce

    Mouleur de chocolats

  • Contrôleur d'arômes
  • 5 325 messages
  • LocationSaint-Leu-d'Esserent / France

Posté 19 mai 2017 - 07:43

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.
RenaudPradenc.com Je suis développeur iOS & Mac indépendant.

#7 Pyroh

Pyroh

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 498 messages

Posté 19 mai 2017 - 10:34

PromiseKit fonctionne exactement comme les promises de JavaScript.

Ça règle souvent beaucoup de soucis et c'est robuste. 



#8 FKDEV

FKDEV

    Broyeur de fèves

  • Artisan chocolatier
  • PipPipPipPipPipPip
  • 1 660 messages

Posté 21 mai 2017 - 09:55

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.


  • Céroce aime ceci





0 utilisateur(s) li(sen)t ce sujet

0 membre(s), 0 invité(s), 0 utilisateur(s) anonyme(s)