Retour d'une valeur asynchrone

Bonjour à tous,
Je m’arrache les cheveux depuis quelques jours…
Depuis mon viewController, je fais appel à une fonction présente dans une autre classe (manager de l’objet que je souhaite manipuler).
Au sein de cette classe(ma classe qui me sert de manager), j’ai donc ma fonction. Celle-ci execute une requête pour récupérer mon objet (j’utilise Parse). Je sais que ma requête s’execute correctement, sauf que le retour de ma fonction se fait avant le retour de ma requête…
J’ai essayé de faire en sorte que la fonction ne renvoit rien et d’utiliser un protocole, mais ça ne fonctionne toujours pas… Si quelqu’un a une piste.
Mon code

Classe ViewController :

@IBAction func getEvent(_ sender: Any)
{
myPics = myEventProvider.getEvent()
myEvent.eventPics = myPics
}

Classe EventProvider :

func getEvent() -> [PFFile]
{
let query = PFQuery(className: “Event”)
query.getObjectInBackground(withId: “HmU6wyqM2Y”) { (object, error) ->
Void in
if object != nil && error == nil {
print(object)
self.eventFile = object![“Pics”] as! [PFFile]
print(self.eventFile.count)
}
}
return eventFile
}

Salut @BillyZeBlack,

Pour faire simple, l’exécution de ton retour de fonction se fait en synchrone vis-à-vis de ta fonction et, comme tu le dis, dans le titre, le callback de ta requête se fait en asynchrone.
Le côté asynchrone fait que le callback peut se faire à tout moment, et en générale, il se fait après l’exécution de ta fonction vu que ça peut être du temps pour récupérer les données et te les retourner.

Donc, quand tu attends une info avec un callback, en asynchrone donc, ne fais pas de return de ta donnée dans la fonction.
En fait, supprime ton return (et aussi dans la signature de ta fonction) et, dans ton callback, après l’affectation de la donnée à ta valeur eventFile, mets la ligne suivante :

myEvent.eventPics = myPics

Et supprime cette ligne, que tu viens d’ajouter, de ta fonction getEvent.

C’est bon pour toi ?

1 « J'aime »

Salut @iMrMaximus,
Merci pour ton retour…
J’ai bien compris ton explication concernant les fonctions asynchrones, cependant, en effectuant les modifs que tu m’as conseillé, ça génèrent une erreur, qui me parait logique :
Lorsque je modifie la signature de ma fonction, je ne peux plus attribué de valeur à ma variable depuis la classe qui appelle cette fonction, vu qu’elle ne revoit rien…
De plus, je n’ai pas dans mon manager de variable “myPics” (mais ça ce n’est pas un pbm).
Les modifs que j’ai apporté

Classe ViewController

@IBAction func getEvent(_ sender: Any)
{
myPics = myEventProvider.getEvent
}

Classe EventProvider

func getEvent()
{
let query = PFQuery(className: “Event”)
query.getObjectInBackground(withId: “HmU6wyqM2Y” ) { (object, error) ->
Void in
if object != nil && error == nil {
// print(object)
self.eventFile = object![“Pics”] as! [PFFile]
self.theEvent.eventPics = self.eventFile
// print(self.eventFile.count)
}
}

}
Encore merci…

Salut @BillyZeBlack,

C’est normal, au vu du corps de ta fonction getEvent(_ sender: Any), tu continues d’appeler la fonction getEvent() en t’attendant un retour synchrone.

Appelles juste la fonction getEvent() comme ça :
myEventProvider.getEvent()

Pour être plus précis, oublie l’affection à la variable myPics.
Tu dois avoir une fonction comme ça :

@IBAction func getEvent(_ sender: Any) {
  myEventProvider.getEvent()
}
1 « J'aime »

Salut @iMrMaximus,
Ok, une fois de plus ce que tu me dis me parait très logique, mais j’ai pourtant une question… Pour qu’une variable de la classe qui appelle la fonction getEvent puisse avoir une valeur, je suppose que je dois passer par un “protocole”, je me trompe ?

Désolé @BillyZeBlack mais je ne comprends pas trop ta question :thinking:.
Pourrais-tu préciser, par exemple, en donnant un exemple ? (si possible)

Cependant, j’ai l’impression que tu mélanges les notions de callback et de protocole qui n’ont pas la même utilité :

  • Le callback est un block qui s’exécute en retour d’exécution d’une fonction. Le callback s’exécute donc en asynchrone (comme ici, le callback de la fonction getObjectInBackground prend en paramètre le résultat et une erreur qui sont la résultante de la requête).

  • Le protocol est une interface (comme en java) qu’un object doit respecter quand cet objet veut s’occuper d’un certain rôle d’un autre objet (si un objet veut gérer le comportement d’un UITextField, l’objet doit être le délégateur du UITextField et doit respecter le protocole UITextFieldDelegate).

Est-ce que ces informations t’éclairent davantage ?

Bonsoir @iMrMaximus
Je ne l’aurais pas si bien expliqué, mais j’avais saisie la notion de protocole (en passant merci @Samir ), mais je te remercie d’avoir éclairé ma lanterne concernant celle du “callback”.
Plus qu’un exemple, je te soumets mon pbm :
Mon projet est censé être capable de récupérer des photos depuis la bibliothèque de l’utilisateur et les afficher dans une collectionView => ça c’est ok
Une fois dans ma collectionView, je les sauvegarde dans une BDD, hébergée par Parse => c’est OK
Par contre je souhaite pouvoir récupérer ces photos, depuis la BDD, et les afficher dans ma collectionView => Donc, depuis ma seule et unique vue, via la fonction “func getEvent”, j’appelle la fonction présente dans mon manager de mon objet, l’objectif étant d’alimenter un tableau qui servira de source à ma collectionView. Là ou je bloque, c’est dans la gestion du temps de latence entre le traitement de la requête et l’utilisation des éléments retournés par celle-ci… Et la transmission des ces éléments à ma vue principale (raison pour laquelle je pensais utiliser un protocole)…
Je ne sais pas si c’est plus clair…
Edit : alors j’ai compris comment récupérer mes données de mon manager à ma vue, mais je bloque toujours sur la problématique d’asynchronisation (je ne sais même pas si ça se dit)

Salut @BillyZeBlack,

Pour faire suite à ton “EDIT”, je ne sais pas non plus si “asynchronisation” se dit mais j’aime bien :blush: ;).

Alors, malheureusement, quand une application va récupérer des données que ce soit sur un serveur ou dans une BDD interne (Core Data et Realm pour ne donner que leur nom comme exemple), c’est normal d’avoir des latences.
Cependant, quand l’application en a, la 1ère chose que je t’invite à faire est de le faire comprendre à l’utilisateur (l’objet UIActivityIndicatorView a notamment été créé pour ça, si ce n’est pas son seul intérêt) que quelque chose se passe et de ne pas le bloquer…
Ensuite, reste à toi de tout faire pour réduire au maximum cette latence (afin d’optimiser l’exéprience de l’utilisateur au sein de ton application).

Es-tu rassuré sur le fait d’utiliser l’asynchronisation ? :slight_smile:

Si tu as d’autres questions (même des questions que tu considères bêtes) n’hésite pas :wink:

Bonjour, si j’ai bien compris ton probleme :
tu utilise une fonction pour recuperer des données par le biais d’une fonction asynchrone qui elle meme te donne le resultat par le biais d’une completion.

le plus simple est de mettre ton return dans la completion comme cela tu es sur que ta fonction getEvent() renvoi le resultat uniquement quand les données sont recues, ou bien une erreur le cas echeant, erreur qu’il faudra gerer bien sur
cela te donnerais:

func getEvent() -> [PFFile]
{
let query = PFQuery(className: “Event”)
query.getObjectInBackground(withId: “HmU6wyqM2Y”) { (object, error) ->
Void in
if object != nil && error == nil {
print(object)
self.eventFile = object![“Pics”] as! [PFFile]
print(self.eventFile.count)
return self.eventFile
} else {
// gestion en cas d’erreur
}
}
}

ensuite le plus logique (c’est ce que je fais dans ces cas la), tu creer une completion à ta fonction getEvent() comme cela quand tu l’appelle tu pourra effactuer le code necessaire pour gerer les données attendues seulement quand elles seront là.

Je teste ça de suite…
Edit : @julien.manfredini, j’ai essayé de suivre tes conseils, mais ça ne fonctionnait pas… Pr contre j’ai trouvé une “combine”, qui pour l’instant m’apporte satisfaction… Mon code :
ViewController :
@IBAction func getEvent(_ sender: Any)
{
myEventProvider.getEvent()
}

EventProvider :
je crée un protocole :
protocol EventProviderDelegate {
func test (thePics: [PFFile])
}

Puis son delegate :
var delegate:EventProviderDelegate?

Et enfin ma fonction, au sein de laquelle pour j’utilise mon protocole pour mon tableau via la fonction “test” :
func getEvent()
{
let query = PFQuery(className: “Event”)
query.getObjectInBackground(withId: “HmU6wyqM2Y” ) { (object, error) ->
Void in
if object != nil && error == nil {
self.eventFile = object![“Pics”] as! [PFFile]
if let pics : [PFFile] = self.eventFile {
self.delegate?.arrayOfPfFileToObject(thePics: pics)
}
}
}
}
et de cette manière, j’arrive à faire passer mes données récupérer de la BBD dans mon provider à ma vue… Reste plus qu’à mettre à jour ma collectionView.
En tout cas merci à tous pour le coup de pouce