web-dev-qa-db-fra.com

Meilleur moyen de mettre en cache des images sur l'application ios?

Bientôt, j'ai une NSDictionary avec des URL pour les images que je dois montrer dans ma UITableView. Chaque cellule a un titre et une image. J'avais réussi à rendre cela possible, bien que le défilement prenne du retard, car il semblait que les cellules téléchargeaient leur image chaque fois qu'elles apparaissaient à l'écran ... J'ai cherché un peu et trouvé SDWebImage sur github. Cela fit disparaître le lag-scroll. Je ne suis pas tout à fait sûr de ce que cela a fait, mais je pensais que cela mettait un peu en cache. Mais! Chaque fois que j'ouvre l'application pour la première fois, je ne vois AUCUNE image et je dois faire défiler l'écran vers le bas et revenir en arrière pour qu'elles arrivent. Et si je quitte l'application avec le bouton d'accueil et que je l'ouvre à nouveau, il semble que la mise en cache fonctionne, car les images à l'écran sont visibles. Toutefois, si je fais défiler une cellule vers le bas, la cellule suivante n'a pas d'image. Jusqu'à ce que je fasse défiler et sauvegarder, ou si je clique dessus. Est-ce ainsi que la mise en cache est censée fonctionner? Ou quel est le meilleur moyen de mettre en cache des images téléchargées sur le Web? Les images sont en cours de mise à jour, donc j'étais sur le point de les importer dans le projet, mais j'aime bien avoir la possibilité de mettre à jour les images sans télécharger de mise à jour.

Est-il impossible de charger toutes les images de la vue de table entière à partir du cache (sachant qu'il y a quelque chose dans le cache) au lancement? Est-ce pour cela que je vois parfois des cellules sans images?

Et oui, j'ai du mal à comprendre ce qu'est le cache.

--MODIFIER--

J'ai essayé cela avec seulement des images de la même taille (500x150) et l'erreur d'aspect a disparu. Cependant, lorsque je fais défiler vers le haut ou le bas, il y a des images sur toutes les cellules, mais au début, elles sont fausses. Une fois que la cellule est restée dans la vue pendant quelques millisecondes, la bonne image apparaît. C’est incroyablement agaçant, mais peut-être que cela doit être? .. Il semble qu’il choisisse d’abord le mauvais index dans le cache. Si je fais défiler lentement, je peux voir que les images clignotent d’une image fausse à la bonne. Si je fais défiler rapidement, alors je crois que les mauvaises images sont visibles à tout moment, mais je ne peux pas le savoir en raison du défilement rapide. Lorsque le défilement rapide ralentit et s’arrête, les mauvaises images apparaissent toujours, mais immédiatement après l’arrêt du défilement, les images correctes sont mises à jour. J'ai aussi une classe personnalisée UITableViewCell, mais je n'ai pas fait de grands changements .. Je n'ai pas encore beaucoup lu mon code, mais je ne vois pas ce qui peut ne pas être correct .. Peut-être que j'ai quelque chose dans la mauvais ordre .. J'ai beaucoup programmé en Java, c #, php, etc., mais j'ai du mal à comprendre Objective-c, avec tous les .h et .m ... J'ai aussi `

@interface FirstViewController : UITableViewController{

/**/
NSCache *_imageCache;
}

(parmi d'autres variables) dans FirstViewController.h. N'est-ce pas correct?

Voici ma cellForRowAtIndexPath.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"hallo";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil)
{
    cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}

NSMutableArray *marr = [hallo objectAtIndex:indexPath.section];
NSDictionary *dict = [marr objectAtIndex:indexPath.row];

NSString* imageName = [dict objectForKey:@"Image"];
//NSLog(@"url: %@", imageURL);

UIImage *image = [_imageCache objectForKey:imageName];

if(image)
{
    cell.imageView.image = image;
}
else
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSString* imageURLString = [NSString stringWithFormat:@"example.com/%@", imageName];
        NSURL *imageURL = [NSURL URLWithString:imageURLString];
        UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:imageURL]];

        if(image)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                CustomCell *cell =(CustomCell*)[self.tableView cellForRowAtIndexPath:indexPath];
                if(cell)
                {
                    cell.imageView.image = image;
                }
            });
            [_imageCache setObject:image forKey:imageName];
        }
    });
}

cell.textLabel.text = [dict objectForKey:@"Name"];

return cell;
}
49
Sti

La mise en cache signifie simplement conserver une copie des données dont vous avez besoin afin que vous n'ayez pas à la charger depuis une source plus lente. Par exemple, les microprocesseurs ont souvent une mémoire cache où ils conservent des copies des données pour ne pas avoir à accéder à la RAM, ce qui est beaucoup plus lent. Les disques durs ont souvent des caches de mémoire à partir desquels le système de fichiers peut accéder beaucoup plus rapidement aux blocs de données récemment utilisés.

De même, si votre application charge beaucoup d'images du réseau, il peut être dans votre intérêt de les mettre en cache sur votre appareil au lieu de les télécharger à chaque fois que vous en avez besoin. Il y a beaucoup de façons de le faire - on dirait que vous en avez déjà trouvé un. Vous voudrez peut-être stocker les images que vous téléchargez dans le répertoire/Bibliothèque/Caches de votre application, surtout si vous ne vous attendez pas à ce qu'elles soient modifiées. Le chargement des images à partir du stockage secondaire sera beaucoup, beaucoup plus rapide que le chargement sur le réseau.

Vous pourriez également être intéressé par la classe NSCache peu connue pour conserver en mémoire les images dont vous avez besoin. NSCache fonctionne comme un dictionnaire, mais lorsque la mémoire est saturée, il commence à libérer une partie de son contenu. Vous pouvez d'abord vérifier le cache pour une image donnée, et si vous ne le trouvez pas, vous pouvez alors regarder dans votre répertoire de caches, et si vous ne le trouvez pas, vous pouvez le télécharger. Rien de tout cela n'accélérera le chargement des images sur votre application la première fois que vous l'exécutez, mais une fois que votre application aura téléchargé l'essentiel de ce dont elle a besoin, elle sera beaucoup plus réactive.

69
Caleb

Je pense que Caleb a bien répondu à la question sur la mise en cache. J'allais juste aborder le processus de mise à jour de votre interface utilisateur lorsque vous récupérez des images, par exemple. en supposant que vous avez une NSCache pour vos images appelée _imageCache:

Tout d’abord, définissez une propriété de file d’opérations pour la tableview:

@property (nonatomic, strong) NSOperationQueue *queue;

Puis dans viewDidLoad, initialisez ceci:

self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 4;

Et puis, dans cellForRowAtIndexPath, vous pourriez alors:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ilvcCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // set the various cell properties

    // now update the cell image

    NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved

    UIImage *image = [_imageCache objectForKey:imagename];

    if (image)
    {
        // if we have an cachedImage sitting in memory already, then use it

        cell.imageView.image = image;
    }
    else
    {
        cell.imageView.image = [UIImage imageNamed:@"blank_cell_image.png"];

        // the get the image in the background

        [self.queue addOperationWithBlock:^{

            // get the UIImage

            UIImage *image = [self getImage:imagename];

            // if we found it, then update UI

            if (image)
            {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // if the cell is visible, then set the image

                    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                    if (cell)
                        cell.imageView.image = image;
                }];

                [_imageCache setObject:image forKey:imagename];
            }
        }];
    }

    return cell;
}

Je le mentionne uniquement car j'ai récemment vu quelques exemples de code circuler sur SO qui utilisent GCD pour mettre à jour la propriété UIImageViewimage appropriée, mais, lors de l'envoi de la mise à jour de l'interface utilisateur dans la file d'attente principale, ils utilisent techniques curieuses (par exemple, recharger la cellule ou la table, mettre à jour simplement la propriété image de l’objet cell existant retourné en haut du tableView:cellForRowAtIndexPath (ce qui pose un problème si la ligne a défilé de l’écran et que la cellule a été retirée de la file réutilisé pour une nouvelle ligne), etc.). En utilisant cellForRowAtIndexPath (à ne pas confondre avec tableView:cellForRowAtIndexPath), vous pouvez déterminer si la cellule est toujours visible et/ou si elle s'est peut-être masquée, retirée de la file d'attente et réutilisée.

23
Rob

La solution la plus simple est d’aller avec quelque chose de très utilisé qui a fait l’objet de tests de résistance.

SDWebImage est un outil puissant qui m'a aidé à résoudre un problème similaire et peut facilement être installé avec des cabosses de cacao. En podfile:

platform :ios, '6.1'
pod 'SDWebImage', '~>3.6'

Cache d'installation:

SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image)
{
    // image is not nil if image was found
}];

Cache image:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

https://github.com/rs/SDWebImage

13
Matt Perejda

Pour la partie de la question sur les mauvaises images, c'est à cause de la réutilisation des cellules. La réutilisation des cellules signifie que les cellules existantes ne sont pas visibles (par exemple, les cellules qui sortent de l'écran en haut lorsque vous faites défiler vers le bas sont celles qui reviennent du bas.) images incorrectes. Mais une fois que la cellule apparaît, le code permettant d'extraire la bonne image s'exécute et vous obtenez les bonnes images. 

Vous pouvez utiliser un espace réservé dans la méthode 'prepareForReuse' de la cellule. Cette fonction est principalement utilisée lorsque vous devez réinitialiser les valeurs lorsque la cellule est amenée pour être réutilisée. Définir un espace réservé ici garantira que vous n'obtiendrez aucune image incorrecte.

1
Swasidhant

Je pense que ce sera mieux pour vous utilisateur quelque chose comme DLImageLoader . Plus d'infos -> https://github.com/AndreyLunevich/DLImageLoader-iOS

[[DLImageLoader sharedInstance] loadImageFromUrl:@"image_url_here"
                                   completed:^(NSError *error, UIImage *image) {
                                        if (error == nil) {
                                            imageView.image = image;
                                        } else {
                                            // if we got an error when load an image
                                        }
                                   }];
1
MaeSTRo

La mise en cache des images peut être faite aussi simplement que cela.

ImageService.m

@implementation ImageService{
    NSCache * Cache;
}

const NSString * imageCacheKeyPrefix = @"Image-";

-(id) init {
    self = [super init];
    if(self) {
        Cache = [[NSCache alloc] init];
    }
    return self;
}

/** 
 * Get Image from cache first and if not then get from server
 * 
 **/

- (void) getImage: (NSString *) key
        imagePath: (NSString *) imagePath
       completion: (void (^)(UIImage * image)) handler
    {
    UIImage * image = [Cache objectForKey: key];

    if( ! image || imagePath == nil || ! [imagePath length])
    {
        image = NOIMAGE;  // Macro (UIImage*) for no image 

        [Cache setObject:image forKey: key];

        dispatch_async(dispatch_get_main_queue(), ^(void){
            handler(image);
        });
    }
    else
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0ul ),^(void){

            UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[imagePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];

            if( !image)
            {
                image = NOIMAGE;
            }

            [Cache setObject:image forKey: key];

            dispatch_async(dispatch_get_main_queue(), ^(void){
                handler(image);
            });
        });
    }
}


- (void) getUserImage: (NSString *) userId
           completion: (void (^)(UIImage * image)) handler
{
    [self getImage: [NSString stringWithFormat: @"%@user-%@", imageCacheKeyPrefix, userId]
         imagePath: [NSString stringWithFormat: @"http://graph.facebook.com/%@/picture?type=square", userId]
        completion: handler];
}

SomeViewController.m

    [imageService getUserImage: userId
                    completion: ^(UIImage *image) {
            annotationImage.image = image;
    }];
0
tsuz
////.h file

#import <UIKit/UIKit.h>

@interface UIImageView (KJ_Imageview_WebCache)


-(void)loadImageUsingUrlString :(NSString *)urlString placeholder :(UIImage *)placeholder_image;

@end

//.m file


#import "UIImageView+KJ_Imageview_WebCache.h"


@implementation UIImageView (KJ_Imageview_WebCache)




-(void)loadImageUsingUrlString :(NSString *)urlString placeholder :(UIImage *)placeholder_image
{




    NSString *imageUrlString = urlString;
    NSURL *url = [NSURL URLWithString:urlString];



    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,     NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *getImagePath = [documentsDirectory stringByAppendingPathComponent:[self tream_char:urlString]];

    NSLog(@"getImagePath--->%@",getImagePath);

    UIImage *customImage = [UIImage imageWithContentsOfFile:getImagePath];




    if (customImage)
    {
        self.image = customImage;


        return;
    }
    else
    {
         self.image=placeholder_image;
    }


    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *uploadTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {


        if (error)
        {
            NSLog(@"%@",[error localizedDescription]);

           self.image=placeholder_image;

            return ;
        }

        dispatch_async(dispatch_get_main_queue(), ^{


            UIImage *imageToCache = [UIImage imageWithData:data];



            if (imageUrlString == urlString)
            {

                self.image = imageToCache;
            }



            [self saveImage:data ImageString:[self tream_char:urlString]];

        });




    }];

    [uploadTask resume];



}


-(NSString *)tream_char :(NSString *)string
{
    NSString *unfilteredString =string;

    NSCharacterSet *notAllowedChars = [[NSCharacterSet characterSetWithCharactersInString:@"!@#$%^&*()_+|abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"] invertedSet];
    NSString *resultString = [[unfilteredString componentsSeparatedByCharactersInSet:notAllowedChars] componentsJoinedByString:@""];
    NSLog (@"Result: %@", resultString);



    return resultString;

}

-(void)saveImage : (NSData *)Imagedata ImageString : (NSString *)imageString
{

    NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);

    NSString* documentDirectory = [documentDirectories objectAtIndex:0];
    NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:imageString];




    if (![Imagedata writeToFile:documentDirectoryFilename atomically:NO])
    {
        NSLog((@"Failed to cache image data to disk"));
    }
    else
    {
        NSLog(@"the cachedImagedPath is %@",documentDirectoryFilename);
    }

}

@end


/// call 

 [cell.ProductImage loadImageUsingUrlString:[[ArrProductList objectAtIndex:indexPath.row] valueForKey:@"product_image"] placeholder:[UIImage imageNamed:@"app_placeholder"]];
0
Kausik Jati