38 votes

Chargement d'une image dans UIImage de manière asynchrone

Je suis le développement d'une application iOS 4 avec iOS 5.0 SDK et XCode 4.2.

J'ai à vous montrer quelques post des blogs dans un UITableView. Quand j'ai récupéré toutes les données du service web, j'utilise cette méthode pour créer une UITableViewCell:

- (BlogTableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* cellIdentifier = @"BlogCell";

    BlogTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil)
    {
        NSArray* topLevelObjects =  [[NSBundle mainBundle] loadNibNamed:@"BlogTableViewCell" owner:nil options:nil];

        for(id currentObject in topLevelObjects)
        {
            if ([currentObject isKindOfClass:[BlogTableViewCell class]])
            {
                cell = (BlogTableViewCell *)currentObject;
                break;
            }
        }
    }

    BlogEntry* entry = [blogEntries objectAtIndex:indexPath.row];

    cell.title.text = entry.title;
    cell.text.text = entry.text;
    cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

    return cell;
}

Mais cette ligne:

cell.photo.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:entry.photo]]];

il est tellement lent (à l'entrée.photo dispose d'une url http).

Est-il possible de charger cette image de manière asynchrone? Je pense que c'est difficile parce que tableView:cellForRowAtIndexPath est appelé très souvent.

81voto

LJ Wilson Points 10777

J'ai écrit une classe personnalisée pour faire juste cela, à l'aide de blocs et PGCD:

WebImageOperations.h

#import <Foundation/Foundation.h>

@interface WebImageOperations : NSObject {
}

// This takes in a string and imagedata object and returns imagedata processed on a background thread
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage;
@end

WebImageOperations.m

#import "WebImageOperations.h"
#import <QuartzCore/QuartzCore.h>

@implementation WebImageOperations


+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
    NSURL *url = [NSURL URLWithString:urlString];

    dispatch_queue_t callerQueue = dispatch_get_current_queue();
    dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapp.processsmagequeue", NULL);
    dispatch_async(downloadQueue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];

        dispatch_async(callerQueue, ^{
            processImage(imageData);
        });
    });
    dispatch_release(downloadQueue);
}

@end

Et dans votre - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

// Pass along the URL to the image (or change it if you are loading there locally)
[WebImageOperations processImageDataWithURLString:entry.photo andBlock:^(NSData *imageData) {
    if (self.view.window) {
        UIImage *image = [UIImage imageWithData:imageData];

        cell.photo.image = image;
    }

}];

Il est très rapide et de charger les images à affecter l'INTERFACE utilisateur ou de la vitesse de défilement de la TableView.

* Remarque: Cet exemple suppose que l'ARC est utilisé. Si non, vous aurez besoin pour gérer votre propre communiqués sur les objets)

52voto

bbrame Points 2643

Dans iOS 6 et versions ultérieures, dispatch_get_current_queue donne des avertissements de dépréciation.

Voici une alternative qui est une synthèse de la réponse @ElJay ci-dessus et de l'article de @khanlou ici .

Créez une catégorie sur UIImage:

UIImage + Helpers.h

 @interface UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback;

@end
 

UIImage + Helpers.m

 #import "UIImage+Helpers.h"

@implementation UIImage (Helpers)

+ (void) loadFromURL: (NSURL*) url callback:(void (^)(UIImage *image))callback {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithData:imageData];
            callback(image);
        });
    });
}

@end
 

28voto

tarmes Points 6363

Jetez un coup d’œil à SDWebImage:

https://github.com/rs/SDWebImage

C'est un fantastique ensemble de cours qui gère tout pour vous.

Tim

2voto

Alladinian Points 16627

Oui, c'est relativement facile. L'idée est quelque chose comme:

  1. Créer une mutable tableau pour stocker vos images
  2. Remplir le tableau avec des espaces réservés si vous aimez (ou NSNull objets si vous n'avez pas)
  3. Créer une méthode pour récupérer vos images de manière asynchrone à l'arrière-plan
  4. Lorsqu'une image est arrivé, échangez votre espace réservé avec l'image réelle et faire un [self.tableView reloadData]

J'ai testé cette approche en plusieurs fois et donne d'excellents résultats. Si vous avez besoin d'aide / des exemples avec le async partie (je recommande d'utiliser le pgcd pour qu') faites le moi savoir.

0voto

Kyr Dunenkoff Points 4333

Vous devrez probablement sous-classer votre UIImageView. J'ai récemment réalisé un projet simple pour expliquer cette tâche particulière - chargement d'image asynchrone en arrière-plan - jetez un coup d'œil à mon projet sur GitHub. Plus précisément, regardez la classe KDImageView .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X