Ecran loading
Salut tout le monde.
Voilà j'ai une app qui s'occupe de télécharger des données sur un serveur. Je mets donc une view de chargement pour faire patienter pendant que le tout soit télécharger.
C'est un json que je parse et qui contient donc du texte mais aussi des urls d'image. Je fais donc appel à dataWithContentsOfURL pour télécharger les images. A la fin de ma méthode de parsing de je fais disparaitre mon écran de chargement.
Seul souci, celui-ci disparait avant que les images soient téléchargées car en gros mon UI apparait (bloquée bien entendu) et faut attendre pour ça se débloque (puisque que tout est fait en thread principal).
Je comprends donc pas pourquoi ma vue de chargement disparait alors que j'ai pas fini de télécharger mes image, vu que tout est sur le thread principal.
Donc je pige pas trop là ... ???
Voilà j'ai une app qui s'occupe de télécharger des données sur un serveur. Je mets donc une view de chargement pour faire patienter pendant que le tout soit télécharger.
C'est un json que je parse et qui contient donc du texte mais aussi des urls d'image. Je fais donc appel à dataWithContentsOfURL pour télécharger les images. A la fin de ma méthode de parsing de je fais disparaitre mon écran de chargement.
Seul souci, celui-ci disparait avant que les images soient téléchargées car en gros mon UI apparait (bloquée bien entendu) et faut attendre pour ça se débloque (puisque que tout est fait en thread principal).
Je comprends donc pas pourquoi ma vue de chargement disparait alors que j'ai pas fini de télécharger mes image, vu que tout est sur le thread principal.
Donc je pige pas trop là ... ???
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Faire du métier et en particulier du download ou du parsing dans le thread principal, c'est aller droit dans le mur.
C'est logique, ton download bloque la RunLoop et l'update de l'UI (les méthodes de mise à jour de l'UI n'ont jamais été temps réel) Beurk. Cherche pas plus loin.
NSURLConnection ?
C'est la première chose à utiliser dès qu'on fait du download ! Faire les choses de ce type de manière synchrone est LA chose à éviter.
La bible pour savoir faire des requêtes réseau en ObjC
Et est-ce que l'utilisation de NSOperation peut être judicieux dans mon cas ?
Sinon pour NSOperation tu en penses quoi ? Je m'en suis jamais servi ...
Mais depuis que GCD est là , au contraire je m'en sers assez souvent. D'autant qu'avant GCD et les blocks, pour utiliser une NSOperation fallait sous-classer NSOperation pour surcharger sa méthode "main" et y mettre le code dedans... un peu fastidieux (et pas vraiment différent de l'autre option de créer une sous-classe de NSThread dédiée à l'exécution asynchrone) donc bon j'utilisais jamais.
Mais depuis que GCD et les blocks existent, c'est totalement différent ça change tout : à l'usage c'est bien plus facile à utiliser : Et hop ayé le code est exécuté par une NSOperation sur une NSOperationQueue dédiée, autrement dit dans un autre thread géré par GCD, t'as rien besoin de gérer. Plus fort encore, comme c'est un block et que le compilateur sait automatiquement avec les blocks référencer les variables extérieures au block mais qui sont utilisées à l'intérieur du block, gérant même tout seul le retain de l'objet qu'il référence et son release quand le block est fini... Bah du coup si tu as besoin d'utiliser dans ta NSOperation des variables qui sont définies au moment où tu veux lancer le code, t'as rien à faire. Alors qu'avant fallait prévoir dans la sous-classe de NSOperation des accesseurs pour pouvoir ensuite passer des variables du code principal à la NSOperation pour qu'elle puisse les utiliser, en pensant à faire un retain dessus, etc..., là plus besoin avec les blocks : Bref, depuis GCD au contraire les blocks c'est très pratique et permet de faire du code asynchrone et des opérations en tâche de fond sans se prendre la tête avec les considérations sur les threads & co, et ça c'est super pratique !
Maintenant c'est pas pour ça que j'utilise les NSOperation à tous les coins de rue dans mon code bien sûr. S'il y a déjà des méthodes prévues pour gérer les choses de manière asynchrone, comme c'est le cas avec NSURLConnection, ou des mécanismes de delegate & co, autant utiliser ce qui existe déjà pour faire les choses de façon asynchrone !
---
Par contre perso j'ai fait une classe "NSURLLoader" qui utilise non pas GCD (ça utilise NSURLConnection et son mécanisme de delegate en interne, donc pas de NSOperation ni d'appel explicite à GCD) mais par contre qui utilise les blocks pour rendre plus simple l'utilisation des NSURLRequests (cf exemple plus bas)
Bon par contre c'est dispo qu'à partir de OSX 10.6 ou iOS4, les blocks n'existant pas avant
Je mettrais sans doute ma classe en question sur mon github un jour, avec la doc et tout, mais en gros pour te donner une idée voilà comment elle s'utilise : J'ai une autre variante qui permet de préciser un bout de code (block) à exécuter au fur et à mesure qu'on reçoit des données, par exemple pour mettre à jour une barre de progression de ton interface, etc.
Ca te donne une idée des choses sympa qu'on peut faire avec les blocks. Du coup j'ai plus besoin d'implémenter [tt]connection:didReceiveResponse:[/tt], [tt]connection:didReceiveData:[/tt], [tt]connectionDidFinishLoading[/tt] et tout, ni besoin de dispatcher mon code dans des méthodes différentes un peu partout dans mon .m, ni besoin de mettre par exemple url dans une variable d'instance dans le .h juste pour pouvoir y accéder dans le [tt]connectionDidFinishLoading[/tt] si je voulais l'utiliser, etc, etc. Que du bon donc !
Et je vois pas trop comment faire car si je fais en asynchrone ma boucle va continuer de tourner et mon image sera pas sauvegarder si ? Je sais pas si tu vois ce que je veux dire? Je soulève un problème imaginaire ..
Alors d'une part, c'est pas une bonne chose que de charger la UIImage dans le modèle dans ce cas particulier, mieux vaut ne garder que l'URL dans le modèle, éventuellement garder un pointeur vers une UIImage mais nil au début, et charger l'image que quand elle a besoin d'être affichée. Comme ça tu ne charges pas toutes les images si elles ne sont pas affichées. (Du coup tu peux utiliser les méthodes de delegate de scrollview etc. pour savoir quand charger l'image.
Au final quand tu parses ton XML tu affectes uniquement unArtiste.url, et quand tu vois que tu vas avoir besoin de l'image, tu regardes si unArtiste.image est nil ou pas, s'il est nil tu charges l'image de façon asynchrone et une fois l'image chargée tu la stockes dans unArtiste.image (pour pas avoir à la recharger la prochaine fois).
L'idéal c'est même de se mettre à l'écoute ensuite de la NSNotification "UIApplicationDidReceiveMemoryWarningNotification" et lorsqu'on reçoit la notification, relâcher l'image (et remettre unArtiste.image à nil) pour libérer un peu la mémoire. Tu pourras toujours recharger l'image d'après l'URL plus tard si tu en as besoin.
---
Mais bon, pour revenir au download même de l'image, il suffit d'implémenter les méthodes de delegate de NSURLConnection dans ta classe Artiste tout simplement (si ton architecture MVC est bien faite et ton modèle proprement pensé ça ne pose aucun problème). Tu prévois dans ta classe "Artiste" une méthode "loadImage" par exemple, qui va créer un NSURLConnection, lancer la connexion asynchrone des données en utilisant self.url, et quand le download est fini (connectionDidFinishLoading:) là tu peux construire ton UIImage à partir des NSData reçues et l'affecter à self.image et basta !
C'est 3x rien à faire, y'a que du copier/coller les bouts de code déjà rédigés dans le URL Loading Programming Guide en plus !
dans le cas des blocks + GCD, il faut quand meme synchroniser les variables partagées, ou mieux faire des structs lockfree et c'est la veritable difficulté du multithread.
(Et puis sinon si tu n'as pas tu ne peux pas prévoir des classes modèle dédiées pour tout ce qui est partie fonctionnelle de ton MVC ?)
Et donc il faudrait une classe qui représente juste mon entité et ensuite une autre classe juste pour manipuler un objet de cette entité et en gros je ne passerai ensuite que par cette dernière classe ?
Non. Crée une sous-classe de NSManagedObject. Dans le Modèle, il faut aussi spécifier le nom de la classe associée à l'entité, pour que Core Data sache quel type d'objet instancier. Regarde les sessions de WWDC 2010 consacrées à Core Data, ça te fera gagner du temps (session 118 "Mastering Core Data").