Il est pratique courante dans les bibliothèques en réseau de nombreux dans de nombreuses langues pour frayer threads pour gérer la communication asynchrone dans le fond tandis que le reste du programme continue de fonctionner. Bien que cette approche fonctionne sur l'iPhone, c'est vraiment pas la bonne façon de le faire. J'ai récemment exécuté sur deux blogues différents qui montrent comment télécharger des images pour les afficher dans une vue de tableau iPhone, et ils ont tous deux fait des communications réseau synchrone utilisant des threads.
Voici mon message d'intérêt public pour aujourd'hui: Ne pas. S'il vous plaît ... juste, s'il vous plaît ne pas, ok? Ne pas reproduire les discussions pour les communications asynchrones à moins vous auriez utilisé les discussions dans la même situation s'il n'y avait pas de code réseau..
Mais d'abord, pourquoi ne devrions-nous utiliser les threads de cette façon? Eh bien, tout simplement, les discussions ont généraux. Ce n'est pas un surcoût énorme, mais il est non-nul, et en fait, il est non triviale donnée la puissance de traitement et de la mémoire physique disponible sur les appareils iPhone OS. Les frais généraux de filetage rend absoulutely sens dans certaines situations; réseau asynchrone n'est pas une de ces situations. Apple fournit des API asynchrones spécifiquement à cette fin et ils sont conçus pour donner le meilleur rendement à la lumière des ressources système disponibles.
Mais, ne pas prendre mon mot pour lui. Voici ce que Apple dit sur la question, dans un guide écrit pour aider les programmeurs de Windows à faire la transition à la programmation pour le Mac (c'est moi qui souligne):
Comme vous le savez probablement, le thread principal de votre application (celui qui est créé automatiquement lorsque votre application est lancée) a une boucle (contenues dans UIApplication) qui est toujours en marche. C'est ce qu'on appelle votre application fonctionner en boucle et il continue de fonctionner aussi longtemps que votre application s'exécute. Chaque fois que dans cette boucle, ne UIApplication tout un tas de trucs. Il vérifie pour les entrées à partir du matériel et des messages Mach de l'OS, par exemple, et il attend de voir s'il ya des course NSTimers dont les objectifs doivent être appelés. Parmi les tâches qu'il fait est de vérifier les données du réseau entrant. Il ya plus de choses de la boucle s'exécutent poignées, mais que ce dernier est le important pour la discussion d'aujourd'hui.
Conceptuellement parlant, CFNetwork n'est pas le plus simple cadre à saisir, mais il est extrêmement efficace. Il peut gérer des centaines, voire des milliers de téléchargements simultanés sans causer le thread principal pour gripper. C'est beaucoup plus efficace que l'aide d'appels de réseau synchrone sur un fil et pas vraiment plus difficile une fois que vous savez comment le faire.
J'ai écrit un iPhone app petits que les téléchargements à partir des images Deviant Art. Vous avez juste saisir le nom de quelqu'un Deviant Art dans un champ texte et l'application va récupérer les images dans la galerie de la cette personne, de les télécharger de manière asynchrone, et afficher chacune dans le tableau une fois qu'il a fini le téléchargement:

Notez que il ya un indicateur d'activité montrant que certaines images sont en train d'être téléchargé. Tout cela se passe sur le thread principal. Je ne fraient discussions explicites dans la présente demande, et pourtant, l'interface reste réactive. Vous pouvez aussi taper une ligne pour afficher l'image plus grande, mais ce n'est pas vraiment important pour le processus étant illustré.
Dans vos propres applications, vous pouvez aborder les choses un peu différemment, mais pour cet exemple d'application, je vais mettre le code pour télécharger l'image dans sa catégorie propre modèle de données appelé DeviantDownload. Je vais remplacer la méthode d'accès pour l'image de sorte que la première fois que l'accesseur est appelée, elle débutera le téléchargement. Si j'appelle la méthode accesseur image et obtenir nulle, le code appelant peut se faire déléguer le téléchargement pour être averti quand le téléchargement est terminé.
Dans cette application, les téléchargements ne sont pas déclenchées jusqu'à la première fois une image défile sur l'écran. C'est peut-être pas ce que vous voulez dans votre application, mais il faudra garder les choses simples et belles pour cet exemple. Voici la déclaration de notre classe de téléchargement:
Assez simple, non? Je définis un domaine d'erreur et un code d'erreur pour la situation d'une erreur de notre application génère (le reste provient de NSURLConnection). Après cela, je déclare un protocole qui définit les méthodes de notre délégué a à mettre en œuvre. Il ya seulement deux: l'un qui est appelée lorsque le téléchargement se termine avec succès, et un autre qui est appelée, si elle échoue. Dans l'exemple d'application, notre vérification d'erreur est minime, mais vous ferez erreur réelle vérifiant dans vos applications, non? DROIT?! Oui, vous, parce que c'est la façon dont vous roulez. Dans une application réelle, vous pouvez également ajouter une méthode troisième délégué que pour informer le délégué quel pourcentage de la fin du téléchargement, mais je vais laisser ça un comme un exercice pour l'élève.
Ensuite, je déclare l'objet DeviantDownload, qui n'a que quatre variables des instances, dont l'un est privé. urlString est l'url de l'image à télécharger, l'image est l'image téléchargée, et le délégué est l'objet qui se veut être notifié sur l'état du téléchargement. Enfin, receivedData est juste un tampon pour contenir les données jusqu'à ce que je télécharge tout avoir et ne peut créer l'image d'elle.
Bon, regardons la mise en œuvre de notre objet de téléchargement:
Pour rendre plus lisible la gestion de mémoire, j'ai d'abord déclarer une propriété pour receivedData dans une extension de classe. Cela crée une propriété qui est accessible dans ma classe, mais ce n'est pas annoncée à d'autres classes. Après cela, j'ai synthétiser les quatre propriétés (trois publique déclarée dans l'en-tête et un privé déclarés en extension)
La première méthode que j'ai écrit est un accesseur pour l'image. Rappelez-vous, synthétiser @ crée un accesseur et mutateur si l'on n'existe pas déjà dans votre code, Donc en offrant un accesseur mais pas un mutator, de synthétiser @ créera un mutator pour moi, mais va utiliser cette méthode comme son accesseur. Je ne veux pas de télécharger plus d'une fois, donc je utiliser la variable d'instance téléchargement de garder une trace du fait que j'ai déjà démarré un téléchargement. Si l'image est nulle et je n'ai pas déjà lancé un téléchargement, j'ai un coup de pied hors de manière asynchrone.
A la fin de la méthode, je retourne l'image, même si elle sera toujours nulle jusqu'à ce que le téléchargement est terminé. Ce n'est pas grave, cela sera l'indication du code appelant qu'ils devraient devenir délégué du téléchargement du.
La méthode suivante, filename retourne juste la dernière partie de urlString, qui est le nom du fichier. Je ne veux pas d'afficher l'URL complète dans le tableau.
La méthode dealloc est une chose tout à fait standard, donc nous pouvons l'ignorer.
Les quatre prochaines méthodes sont les rappels asynchrones pour NSURLConnection. Le premier, la connexion: didReceiveResponse: est appelée quand une connexion est établie. J'ai mis la longueur de receivedData à zéro ici, parce que si il ya une redirection, ce sera appelée plusieurs fois et je tiens à débusquer la charge des connexions antérieures redirigé. Ces données supplémentaires, quand elle existe, serait de prévenir notre image d'être créé.
connexion: didReceiveData: est appelée périodiquement pendant le téléchargement, chaque fois qu'il ya des données assis dans la mémoire tampon pour nous. Tout ce que je fais ici est sec au toucher les données nouvellement reçues sur l'extrémité de ce qui déjà été reçues.
Dans le cadre de: didFailWithError:, je libère la connexion et notifier notre délégué. Parce que c'est un objet de modèle de données, je ne montre pas une alerte ou quoi que ce soit. Faire face à l'erreur n'est pas la responsabilité de cet objet. Une fois qu'il il rend compte, son travail est fait, il vous laisse le contrôleur déterminer quoi faire avec l'erreur. Cela maintient notre objet fermement dans le "modèle" composante du paradigme MVC et maximise les chances je vais être capable de réutiliser cet objet à un certain point.
Enfin, dans connectionDidFinishLoading: je vais créer une image à partir des données reçues, il assigner à la propriété d'image et informer notre délégué.
La dernière méthode, comparer: est mis en œuvre pour nous de déterminer si deux objets DeviantDownload représentent le même fichier. Je vais l'utiliser pour regarder deux téléchargements et voir si elles sont téléchargeant le même fichier, ce qui nous ne voulons pas faire. Deviant Art utilise une batterie de serveurs pour servir des images, donc la même image peut avoir plusieurs URL, mais pour un utilisateur particulier, le nom du fichier doit être unique, donc je compare cela.
Dans le contrôleur de tableau, je tire une liste des URL pour l'utilisateur donné. Je ne vais pas entrer dans les détails de la logique qui fait cela, mais en bref, je asynchrone déroulant données JSON pour la galerie de l'utilisateur dans des morceaux de 24 images, la création d'objets DeviantDownload nouvelle pour chacun d'eux et de les ajouter à la NSMutableArray que les lecteurs de notre table. Dans notre tableView: cellForRowAtIndexPath:, voici ce que je fais:
La première partie de cette méthode est standard slideshows cellule de tableau de vue, mais j'ai ensuite récupérer l'objet DeviantDownload pour la ligne affichée. Si la méthode accesseur image revient nul, je montre l'indicateur d'activité de la cellule (alias «spinning wait-a-cond doohickey s") et lui attribuer le contrôleur comme le délégué de la télécharger. Si l'objet ne retourne pas nul, je m'assure de l'indicateur d'activité n'est pas d'animer l'image et le bâton que le téléchargement de voir l'image dans la cellule. La première fois que cela est appelée pour une ligne particulière, l'image sera de retour nul, ce qui va déclencher le téléchargement pour commencer.
J'ai aussi mettre en œuvre la méthode du délégué DeviantDownload qui est appelée lorsque le téléchargement est terminé:
Tout ce que je fais ici est de trouver l'index de l'objet que j'ai été avisé a fini de télécharger et je construis un chemin indice basé sur elle. Je puis dire la table de recharger cette ligne une cellule. En faisant cela va déclencher tableView: cellForRowAtIndexPath: pour obtenir de nouveau appelée pour la ligne qui a rempli et cette fois que la méthode sera d'obtenir une image valide et arrêtera l'indicateur d'activité et de montrer l'image téléchargée à la place.
Ta da! C'est tout. C'est vraiment pas plus difficile que d'utiliser des fils ou NSOperationQueue fois que vous obtenez une idée de courir en utilisant l'approche basée boucle.
Si vous voulez l'essayer, vous pouvez télécharger le projet complet ici. Comme toujours, le code est gratuit pour une utilisation sans aucune restriction ou limitation, je venais vous demander de ne pas republier tel quel et de demande de crédit pour cela.
Voici mon message d'intérêt public pour aujourd'hui: Ne pas. S'il vous plaît ... juste, s'il vous plaît ne pas, ok? Ne pas reproduire les discussions pour les communications asynchrones à moins vous auriez utilisé les discussions dans la même situation s'il n'y avait pas de code réseau..
Note: Quelques personnes ont suggéré que ce message est trompeur car les threads sont utilisés dans les coulisses. Oui, il est vrai que l'iPhone OS se frayer un petit nombre de non-principales discussions que le résultat de l'utilisation de certaines API de réseau. Vous devriez quand même en utilisant les API asynchrones pour vos communications réseau et ne doivent pas être les discussions de frai de sorte que vous pouvez faire des appels de réseaux synchrones sans blocage.Je n'aime pas à frapper des tutoriels d'autres personnes. En programmation, il ya plusieurs façons de faire presque tout, et souvent plus d'une approche a le mérite, dans certaines situations, donc je suis toujours hésitant à dire que l'approche de quelqu'un d'autre utilise est "mal" si cela fonctionne. Mais, c'est l'un de ces cas où il ya une raison claire et impérieuse de ne pas le faire d'une façon particulière. Cependant, parce que la «bonne» façon n'est pas nécessairement intuitif, surtout pour les personnes dont l'expérience de réseautage a tous été les threads, j'ai pensé qu'il vaut un court tutoriel pour montrer comment télécharger de manière asynchrone pour afficher dans une table sans frai des threads.
Mais d'abord, pourquoi ne devrions-nous utiliser les threads de cette façon? Eh bien, tout simplement, les discussions ont généraux. Ce n'est pas un surcoût énorme, mais il est non-nul, et en fait, il est non triviale donnée la puissance de traitement et de la mémoire physique disponible sur les appareils iPhone OS. Les frais généraux de filetage rend absoulutely sens dans certaines situations; réseau asynchrone n'est pas une de ces situations. Apple fournit des API asynchrones spécifiquement à cette fin et ils sont conçus pour donner le meilleur rendement à la lumière des ressources système disponibles.
Mais, ne pas prendre mon mot pour lui. Voici ce que Apple dit sur la question, dans un guide écrit pour aider les programmeurs de Windows à faire la transition à la programmation pour le Mac (c'est moi qui souligne):
Une des complexités de l'utilisation prise implique l'utilisation de threads séparés pour gérer l'échange de données. Dans un environnement serveur, où des centaines de douilles sont ouvertes simultanément, le code qui vérifie tous ces fils pour l'activité peut entraîner une baisse de performance significative. En outre, avec les threads, la synchronisation des données est souvent un problème, et les serrures sont souvent nécessaires pour garantir qu'un seul thread a accès à une variable globale donnée à la fois.Quand Apple dit quelque chose d'aussi précis que cela, ils ont généralement une raison sacrément bon pour elle. C'est une façon polie de dire Apple "ne pas utiliser les threads pour le réseautage, imbécile". Ils sont beaucoup plus polis que je suis, évidemment.
[...]
Code d'événement axé est plus complexe que le code utilisant des fils de blocage, mais il offre les meilleures performances réseau. Lorsque vous utilisez une boucle courir pour gérer les sockets multiples dans le même thread, vous évitez les problèmes de synchronisation des données associées à une solution multi-thread.
Comme vous le savez probablement, le thread principal de votre application (celui qui est créé automatiquement lorsque votre application est lancée) a une boucle (contenues dans UIApplication) qui est toujours en marche. C'est ce qu'on appelle votre application fonctionner en boucle et il continue de fonctionner aussi longtemps que votre application s'exécute. Chaque fois que dans cette boucle, ne UIApplication tout un tas de trucs. Il vérifie pour les entrées à partir du matériel et des messages Mach de l'OS, par exemple, et il attend de voir s'il ya des course NSTimers dont les objectifs doivent être appelés. Parmi les tâches qu'il fait est de vérifier les données du réseau entrant. Il ya plus de choses de la boucle s'exécutent poignées, mais que ce dernier est le important pour la discussion d'aujourd'hui.
Conceptuellement parlant, CFNetwork n'est pas le plus simple cadre à saisir, mais il est extrêmement efficace. Il peut gérer des centaines, voire des milliers de téléchargements simultanés sans causer le thread principal pour gripper. C'est beaucoup plus efficace que l'aide d'appels de réseau synchrone sur un fil et pas vraiment plus difficile une fois que vous savez comment le faire.
Essayons téléchargement sur le thread principal
J'ai écrit un iPhone app petits que les téléchargements à partir des images Deviant Art. Vous avez juste saisir le nom de quelqu'un Deviant Art dans un champ texte et l'application va récupérer les images dans la galerie de la cette personne, de les télécharger de manière asynchrone, et afficher chacune dans le tableau une fois qu'il a fini le téléchargement:
Notez que il ya un indicateur d'activité montrant que certaines images sont en train d'être téléchargé. Tout cela se passe sur le thread principal. Je ne fraient discussions explicites dans la présente demande, et pourtant, l'interface reste réactive. Vous pouvez aussi taper une ligne pour afficher l'image plus grande, mais ce n'est pas vraiment important pour le processus étant illustré.
Note:Bien que la plupart des galeries DeviantArt sont SFW, il ya beaucoup qui ne sont pas, si juste être conscient de cela. Edit: Met hors de cet avertissement était inutile - car nous n'avons pas authentifié, filtres DeviartArt sur toutes les images NSFW automatiquement.
Création de l'objet Téléchargement
Dans vos propres applications, vous pouvez aborder les choses un peu différemment, mais pour cet exemple d'application, je vais mettre le code pour télécharger l'image dans sa catégorie propre modèle de données appelé DeviantDownload. Je vais remplacer la méthode d'accès pour l'image de sorte que la première fois que l'accesseur est appelée, elle débutera le téléchargement. Si j'appelle la méthode accesseur image et obtenir nulle, le code appelant peut se faire déléguer le téléchargement pour être averti quand le téléchargement est terminé.
Dans cette application, les téléchargements ne sont pas déclenchées jusqu'à la première fois une image défile sur l'écran. C'est peut-être pas ce que vous voulez dans votre application, mais il faudra garder les choses simples et belles pour cet exemple. Voici la déclaration de notre classe de téléchargement:
#import <Foundation/Foundation.h>
#define DeviantDownloadErrorDomain @"Deviant Download Error Domain"
enum
{ DeviantDownloadErrorNoConnection = 1000,
};
@class DeviantDownload;
@protocol DeviantDownloadDelegate
- (void)downloadDidFinishDownloading:(DeviantDownload *)download;
- (void)download:(DeviantDownload *)download didFailWithError:(NSError *)error;
@end
@interface DeviantDownload : NSObject
{ NSString
*urlString; UIImage
*image; id <NSObject, DeviantDownloadDelegate> delegate;
@private NSMutableData
*receivedData; BOOL
downloading;
}
@property (nonatomic, retain) NSString *urlString;
@property (nonatomic, retain) UIImage *image;
@property (nonatomic, readonly) NSString *filename;
@property (nonatomic, assign) id <NSObject, DeviantDownloadDelegate> delegate;
@endAssez simple, non? Je définis un domaine d'erreur et un code d'erreur pour la situation d'une erreur de notre application génère (le reste provient de NSURLConnection). Après cela, je déclare un protocole qui définit les méthodes de notre délégué a à mettre en œuvre. Il ya seulement deux: l'un qui est appelée lorsque le téléchargement se termine avec succès, et un autre qui est appelée, si elle échoue. Dans l'exemple d'application, notre vérification d'erreur est minime, mais vous ferez erreur réelle vérifiant dans vos applications, non? DROIT?! Oui, vous, parce que c'est la façon dont vous roulez. Dans une application réelle, vous pouvez également ajouter une méthode troisième délégué que pour informer le délégué quel pourcentage de la fin du téléchargement, mais je vais laisser ça un comme un exercice pour l'élève.
Ensuite, je déclare l'objet DeviantDownload, qui n'a que quatre variables des instances, dont l'un est privé. urlString est l'url de l'image à télécharger, l'image est l'image téléchargée, et le délégué est l'objet qui se veut être notifié sur l'état du téléchargement. Enfin, receivedData est juste un tampon pour contenir les données jusqu'à ce que je télécharge tout avoir et ne peut créer l'image d'elle.
Bon, regardons la mise en œuvre de notre objet de téléchargement:
#import "DeviantDownload.h"
@interface DeviantDownload()
@property (nonatomic, retain) NSMutableData *receivedData;
@end
@implementation DeviantDownload
@synthesize urlString;
@synthesize image;
@synthesize delegate;
@synthesize receivedData;
#pragma mark -
- (UIImage *)image
{
if (image == nil && !downloading)
{
if (urlString != nil && [urlString length] > 0)
{
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:self.urlString]];
NSURLConnection *con = [[NSURLConnection alloc]
initWithRequest:req
delegate:self
startImmediately:NO];
[con scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[con start];
if (con)
{
NSMutableData *data = [[NSMutableData alloc] init];
self.receivedData=data;
[data release];
}
else
{
NSError *error = [NSError errorWithDomain:DeviantDownloadErrorDomain
code:DeviantDownloadErrorNoConnection
userInfo:nil];
if ([self.delegate respondsToSelector:@selector(download:didFailWithError:)])
[delegate download:self didFailWithError:error];
}
[req release];
downloading = YES;
}
}
return image;
}
- (NSString *)filename
{
return [urlString lastPathComponent];
}
- (void)dealloc
{
[urlString release];
[image release];
delegate = nil;
[receivedData release];
[super dealloc];
}
#pragma mark -
#pragma mark NSURLConnection Callbacks
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[connection release];
if ([delegate respondsToSelector:@selector(download:didFailWithError:)])
[delegate download:self didFailWithError:error];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.image = [UIImage imageWithData:receivedData];
if ([delegate respondsToSelector:@selector(downloadDidFinishDownloading:)])
[delegate downloadDidFinishDownloading:self];
[connection release];
self.receivedData = nil;
}
#pragma mark -
#pragma mark Comparison
- (NSComparisonResult)compare:(id)theOther
{
DeviantDownload *other = (DeviantDownload *)theOther;
return [self.filename compare:other.filename];
}
@end
Pour rendre plus lisible la gestion de mémoire, j'ai d'abord déclarer une propriété pour receivedData dans une extension de classe. Cela crée une propriété qui est accessible dans ma classe, mais ce n'est pas annoncée à d'autres classes. Après cela, j'ai synthétiser les quatre propriétés (trois publique déclarée dans l'en-tête et un privé déclarés en extension)
La première méthode que j'ai écrit est un accesseur pour l'image. Rappelez-vous, synthétiser @ crée un accesseur et mutateur si l'on n'existe pas déjà dans votre code, Donc en offrant un accesseur mais pas un mutator, de synthétiser @ créera un mutator pour moi, mais va utiliser cette méthode comme son accesseur. Je ne veux pas de télécharger plus d'une fois, donc je utiliser la variable d'instance téléchargement de garder une trace du fait que j'ai déjà démarré un téléchargement. Si l'image est nulle et je n'ai pas déjà lancé un téléchargement, j'ai un coup de pied hors de manière asynchrone.
A la fin de la méthode, je retourne l'image, même si elle sera toujours nulle jusqu'à ce que le téléchargement est terminé. Ce n'est pas grave, cela sera l'indication du code appelant qu'ils devraient devenir délégué du téléchargement du.
La méthode suivante, filename retourne juste la dernière partie de urlString, qui est le nom du fichier. Je ne veux pas d'afficher l'URL complète dans le tableau.
La méthode dealloc est une chose tout à fait standard, donc nous pouvons l'ignorer.
Les quatre prochaines méthodes sont les rappels asynchrones pour NSURLConnection. Le premier, la connexion: didReceiveResponse: est appelée quand une connexion est établie. J'ai mis la longueur de receivedData à zéro ici, parce que si il ya une redirection, ce sera appelée plusieurs fois et je tiens à débusquer la charge des connexions antérieures redirigé. Ces données supplémentaires, quand elle existe, serait de prévenir notre image d'être créé.
connexion: didReceiveData: est appelée périodiquement pendant le téléchargement, chaque fois qu'il ya des données assis dans la mémoire tampon pour nous. Tout ce que je fais ici est sec au toucher les données nouvellement reçues sur l'extrémité de ce qui déjà été reçues.
Dans le cadre de: didFailWithError:, je libère la connexion et notifier notre délégué. Parce que c'est un objet de modèle de données, je ne montre pas une alerte ou quoi que ce soit. Faire face à l'erreur n'est pas la responsabilité de cet objet. Une fois qu'il il rend compte, son travail est fait, il vous laisse le contrôleur déterminer quoi faire avec l'erreur. Cela maintient notre objet fermement dans le "modèle" composante du paradigme MVC et maximise les chances je vais être capable de réutiliser cet objet à un certain point.
Enfin, dans connectionDidFinishLoading: je vais créer une image à partir des données reçues, il assigner à la propriété d'image et informer notre délégué.
La dernière méthode, comparer: est mis en œuvre pour nous de déterminer si deux objets DeviantDownload représentent le même fichier. Je vais l'utiliser pour regarder deux téléchargements et voir si elles sont téléchargeant le même fichier, ce qui nous ne voulons pas faire. Deviant Art utilise une batterie de serveurs pour servir des images, donc la même image peut avoir plusieurs URL, mais pour un utilisateur particulier, le nom du fichier doit être unique, donc je compare cela.
Utilisation de l'objet Téléchargement
Dans le contrôleur de tableau, je tire une liste des URL pour l'utilisateur donné. Je ne vais pas entrer dans les détails de la logique qui fait cela, mais en bref, je asynchrone déroulant données JSON pour la galerie de l'utilisateur dans des morceaux de 24 images, la création d'objets DeviantDownload nouvelle pour chacun d'eux et de les ajouter à la NSMutableArray que les lecteurs de notre table. Dans notre tableView: cellForRowAtIndexPath:, voici ce que je fais:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
DeviantDisplayCell *cell = (DeviantDisplayCell *)[tableView dequeueReusableCellWithIdentifier:[DeviantDisplayCell reuseIdentifier]];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:@"DeviantDisplayCell" owner:self options:nil];
cell = loadCell;
self.loadCell = nil;
}
DeviantDownload *download = [downloads objectAtIndex:[indexPath row]];
cell.cellLabel.text = download.filename;
UIImage *cellImage = download.image;
if (cellImage == nil)
{
[cell.cellSpinner startAnimating];
download.delegate = self;
}
else
[cell.cellSpinner stopAnimating];
cell.cellImageView.image = cellImage;
return cell;
}La première partie de cette méthode est standard slideshows cellule de tableau de vue, mais j'ai ensuite récupérer l'objet DeviantDownload pour la ligne affichée. Si la méthode accesseur image revient nul, je montre l'indicateur d'activité de la cellule (alias «spinning wait-a-cond doohickey s") et lui attribuer le contrôleur comme le délégué de la télécharger. Si l'objet ne retourne pas nul, je m'assure de l'indicateur d'activité n'est pas d'animer l'image et le bâton que le téléchargement de voir l'image dans la cellule. La première fois que cela est appelée pour une ligne particulière, l'image sera de retour nul, ce qui va déclencher le téléchargement pour commencer.
J'ai aussi mettre en œuvre la méthode du délégué DeviantDownload qui est appelée lorsque le téléchargement est terminé:
- (void)downloadDidFinishDownloading:(DeviantDownload *)download
{
NSUInteger index = [downloads indexOfObject:download];
NSUInteger indices[] = {0, index};
NSIndexPath *path = [[NSIndexPath alloc] initWithIndexes:indices length:2];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationNone];
[path release];
download.delegate = nil;
}Tout ce que je fais ici est de trouver l'index de l'objet que j'ai été avisé a fini de télécharger et je construis un chemin indice basé sur elle. Je puis dire la table de recharger cette ligne une cellule. En faisant cela va déclencher tableView: cellForRowAtIndexPath: pour obtenir de nouveau appelée pour la ligne qui a rempli et cette fois que la méthode sera d'obtenir une image valide et arrêtera l'indicateur d'activité et de montrer l'image téléchargée à la place.
Ta da! C'est tout. C'est vraiment pas plus difficile que d'utiliser des fils ou NSOperationQueue fois que vous obtenez une idée de courir en utilisant l'approche basée boucle.
Si vous voulez l'essayer, vous pouvez télécharger le projet complet ici. Comme toujours, le code est gratuit pour une utilisation sans aucune restriction ou limitation, je venais vous demander de ne pas republier tel quel et de demande de crédit pour cela.
Aucun commentaire:
Enregistrer un commentaire