Domanda Il modo migliore per memorizzare nella cache le immagini sull'app ios?


In breve, ho un NSDictionary con gli URL per le immagini che ho bisogno di mostrare nel mio UITableView. Ogni cella ha un titolo e un'immagine. Avevo fatto in modo che ciò accadesse, anche se lo scorrimento era in ritardo, poiché sembrava che le celle avessero scaricato l'immagine ogni volta che entravano sullo schermo. Ho cercato un po 'e ho trovato SDWebImage su github. Questo ha fatto sparire la scroll-lagg. Non sono completamente sicuro di quello che ha fatto, ma ho creduto che facesse un po 'di cache. Ma! Ogni volta che apro l'app per la prima volta, vedo NESSUNA immagine, e devo scorrere verso il basso, e il backup per loro di arrivare. E se esco dall'app con il tasto home e apro di nuovo, sembra che la cache funzioni, perché le immagini sullo schermo sono visibili, tuttavia se faccio scorrere una cella verso il basso, la cella successiva non ha alcuna immagine. Fino a quando non lo faccio scorrere oltre il backup, o se clicco su di esso. È così che dovrebbe funzionare il caching? O qual è il modo migliore per memorizzare le immagini scaricate dal web? Le immagini sono state aggiornate rariamente, quindi ero vicino a importarle semplicemente nel progetto, ma mi piace avere la possibilità di aggiornare le immagini senza caricare un aggiornamento ..

È impossibile caricare tutte le immagini per l'intera tableview dalla cache (dato che c'è qualcosa nella cache) al momento del lancio? È per questo che a volte vedo cellule senza immagini?

E sì, ho difficoltà a capire cosa sia la cache.

--MODIFICARE--

Ho provato questo con solo le immagini della stessa dimensione (500x150), e l'errore di aspetto è sparito, tuttavia quando scorro verso l'alto o verso il basso, ci sono immagini su tutte le celle, ma all'inizio si sbagliano. Dopo che la cella è stata visualizzata per alcuni millisecondi, viene visualizzata l'immagine corretta. Questo è incredibilmente fastidioso, ma forse come deve essere? Sembra che scelga l'indice sbagliato dalla cache all'inizio. Se si scorre lentamente, posso vedere le immagini lampeggiare dall'immagine sbagliata a quella corretta. Se faccio scorrere velocemente, credo che le immagini sbagliate siano sempre visibili, ma non posso dire a causa dello scorrimento veloce. Quando lo scorrimento veloce rallenta e alla fine si interrompe, le immagini errate appaiono ancora, ma immediatamente dopo che si interrompe lo scorrimento, si aggiorna alle immagini giuste. Ho anche una mia abitudine UITableViewCell classe, ma non ho apportato grossi cambiamenti .. Non ho ancora approfondito il mio codice, ma non riesco a pensare a cosa potrebbe essere sbagliato .. Forse ho qualcosa nell'ordine sbagliato. Ho programmato molto in java, c #, php ecc., ma ho difficoltà a capire Objective-c, con tutto il .h e .m ... Ho anche `

@interface FirstViewController : UITableViewController{

/**/
NSCache *_imageCache;
}

(tra le altre variabili) in FirstViewController.h. Non è corretto?

Ecco il mio 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;
}

44
2017-07-16 19:52


origine


risposte:


Caching significa semplicemente conservare una copia dei dati di cui hai bisogno in modo da non doverla caricare da una sorgente più lenta. Ad esempio, i microprocessori hanno spesso memoria cache in cui conservano le copie dei dati in modo che non debbano accedere alla RAM, che è molto più lenta. I dischi rigidi spesso dispongono di cache di memoria da cui il file system può accedere molto più rapidamente ai blocchi di dati a cui è stato effettuato l'accesso di recente.

Allo stesso modo, se la tua app carica un sacco di immagini dalla rete, potrebbe essere nel tuo interesse memorizzarle in cache sul tuo dispositivo invece di scaricarle ogni volta che ne hai bisogno. Ci sono molti modi per farlo: sembra che tu ne abbia già trovato uno. Potresti voler memorizzare le immagini che scarichi nella directory dell'app / Libreria / Cache, specialmente se non ti aspetti che cambi. Caricare le immagini dallo storage secondario sarà molto, molto più veloce che caricarle sulla rete.

Potresti anche essere interessato al poco conosciuto NSCache classe per conservare le immagini che ti servono in memoria. NSCache funziona come un dizionario, ma quando la memoria diventa stretta inizierà a rilasciare alcuni dei suoi contenuti. Puoi controllare prima la cache di una determinata immagine e, se non la trovi lì, puoi guardare nella directory delle cache e, se non la trovi lì, puoi scaricarla. Nulla di tutto questo accelera il caricamento delle immagini sulla tua app la prima volta che la esegui, ma una volta che l'app ha scaricato la maggior parte di ciò di cui ha bisogno, sarà molto più reattiva.


63
2017-07-16 20:10



Penso che Caleb abbia risposto bene alla domanda sulla cache. Stavo andando a toccare il processo per aggiornare l'interfaccia utente mentre recuperi le immagini, ad es. supponendo che tu abbia un NSCache per le tue immagini chiamate _imageCache:

Innanzitutto, definire una proprietà della coda di operazioni per la tabella:

@property (nonatomic, strong) NSOperationQueue *queue;

Quindi in viewDidLoad, inizializza questo:

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

E poi dentro cellForRowAtIndexPath, potresti quindi:

- (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;
}

Lo dico solo perché recentemente ho visto alcuni esempi di codice fluttuare su SO che utilizzano GCD per aggiornare l'appropriato UIImageView  image proprietà, ma nel processo di invio dell'aggiornamento dell'interfaccia utente alla coda principale, impiegano tecniche curiose (ad es., ricaricando la cella o la tabella, semplicemente aggiornando la proprietà dell'immagine dell'esistente). cell oggetto restituito nella parte superiore del tableView:cellForRowAtIndexPath (che è un problema se la riga si è spostata dallo schermo e la cella è stata rimossa dalla coda e riutilizzata per una nuova riga), ecc.). Usando cellForRowAtIndexPath (da non confondere con tableView:cellForRowAtIndexPath), puoi determinare se la cella è ancora visibile e / o se potrebbe essersi scrollata di dosso e essere stata deselezionata e riutilizzata.


18
2017-07-16 20:25



La soluzione più semplice è quella di usare qualcosa di molto usato che è stato sottoposto a stress test.

SDWebImage è un potente strumento che mi ha aiutato a risolvere un problema simile e può essere facilmente installato con cialde di cacao. Nel podfile:

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

Cache di installazione:

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

Immagine della cache:

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

https://github.com/rs/SDWebImage


12
2017-10-24 16:53



Penso che per te sarà meglio qualcosa come DLImageLoader. Maggiori informazioni -> 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
2017-08-14 11:22



Per la parte della domanda relativa alle immagini errate, è a causa del riutilizzo delle celle. Riutilizzare le celle significa che le celle esistenti, che non sono visibili (ad esempio, le celle che escono dallo schermo in alto quando si scorre verso il basso sono quelle che tornano dal fondo.) E così si ottiene immagini errate. Ma una volta che la cella si presenta, il codice per il recupero dell'immagine corretta viene eseguito e si ottengono le immagini corrette.

Puoi usare un segnaposto nel metodo 'prepareForReuse' della cella. Questa funzione è utilizzata principalmente quando è necessario ripristinare i valori quando la cella viene richiamata per il riutilizzo. L'impostazione di un segnaposto qui farà in modo che non si otterranno immagini errate.


1
2018-06-01 13:46



Le immagini di cache possono essere fatte semplicemente come questo.

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
2018-01-06 01:34



////.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
2017-12-07 10:45