¿La mejor forma de almacenar imágenes en la aplicación ios?

En breve, tengo un NSDictionary con URL para las imágenes que necesito mostrar en mi UITableView . Cada celda tiene un título y una imagen. Lo había logrado con éxito, aunque el desplazamiento estaba rezagado, ya que parecía que las células descargaban su imagen cada vez que entraban en la pantalla. Busqué un poco y encontré SDWebImage en github. Esto hizo que el scroll-lagg desapareciera. No estoy completamente seguro de lo que hizo, pero creo que hizo un poco de almacenamiento en caché. ¡Pero! Cada vez que abro la aplicación por primera vez, NO veo imágenes, y tengo que desplazarme hacia abajo y hacer una copia de seguridad para que lleguen. Y si salgo de la aplicación con el botón de inicio y vuelvo a abrir, parece que el almacenamiento en caché está funcionando, porque las imágenes en la pantalla son visibles, sin embargo, si desplazo una celda hacia abajo, la siguiente celda no tiene imagen. Hasta que me desplace y haga una copia de seguridad, o si hago clic en él. ¿Es así como se supone que el almacenamiento en caché funciona? ¿O cuál es la mejor manera de almacenar en caché las imágenes descargadas de la web? Las imágenes se están actualizando de forma muy rápida, así que estuve cerca de importarlas al proyecto, pero me gusta tener la posibilidad de actualizar las imágenes sin cargar una actualización.

¿Es imposible cargar todas las imágenes para la vista de tabla completa de la memoria caché (dado que hay algo en la memoria caché) en el momento del lanzamiento? ¿Es por eso que a veces veo celdas sin imágenes?

Y sí, estoy teniendo dificultades para entender qué es el caché.

–EDITAR–

Intenté esto solo con imágenes del mismo tamaño (500×150), y el aspecto de error desapareció, sin embargo, cuando me desplazo hacia arriba o hacia abajo, hay imágenes en todas las celdas, pero al principio están equivocadas. Después de que la celda ha estado en la vista durante algunos milisegundos, aparece la imagen correcta. Esto es increíblemente molesto, pero ¿cómo debe ser? Parece que al principio elige el índice incorrecto del caché. Si me muevo despacio, entonces puedo ver que las imágenes parpadean desde una imagen incorrecta a la correcta. Si me desplazo rápidamente, creo que las imágenes incorrectas son visibles en todo momento, pero no puedo verlas debido al rápido desplazamiento. Cuando el desplazamiento rápido se ralentiza y finalmente se detiene, las imágenes incorrectas siguen apareciendo, pero inmediatamente después de que deja de desplazarse, se actualiza a las imágenes correctas. También tengo una clase UITableViewCell personalizada, pero no he hecho ningún cambio importante. No he UITableViewCell mucho mi código todavía, pero no puedo pensar en qué puede estar mal. Tal vez tenga algo en el orden incorrecto .. He progtwigdo mucho en java, c #, php, etc., pero estoy teniendo dificultades para entender Objective-c, con todos los .h y .m … También he `

 @interface FirstViewController : UITableViewController{ /**/ NSCache *_imageCache; } 

(entre otras variables) en FirstViewController.h . ¿Esto no es correcto?

Aquí está mi 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; } 

El almacenamiento en caché solo significa guardar una copia de los datos que necesita para que no tenga que cargarlos desde una fuente más lenta. Por ejemplo, los microprocesadores a menudo tienen memoria caché donde guardan copias de datos para que no tengan que acceder a la RAM, que es mucho más lenta. Los discos duros a menudo tienen cachés de memoria desde los cuales el sistema de archivos puede obtener un acceso mucho más rápido a los bloques de datos a los que se ha accedido recientemente.

Del mismo modo, si su aplicación carga muchas imágenes de la red, puede interesarle almacenarlas en su dispositivo en lugar de descargarlas cada vez que las necesite. Hay muchas formas de hacerlo, parece que ya encontraste una. Es posible que desee almacenar las imágenes que descarga en el directorio / Library / Caches de su aplicación, especialmente si no espera que cambien. Cargar las imágenes desde el almacenamiento secundario será mucho, mucho más rápido que cargarlas a través de la red.

También podría estar interesado en la poco conocida clase NSCache para mantener en la memoria las imágenes que necesita. NSCache funciona como un diccionario, pero cuando la memoria se agota, comenzará a liberar algunos de sus contenidos. Primero puede verificar la caché de una imagen determinada y, si no la encuentra allí, puede buscarla en el directorio de cachés y, si no la encuentra allí, puede descargarla. Nada de esto acelerará la carga de imágenes en su aplicación la primera vez que la ejecuta, pero una vez que su aplicación haya descargado la mayor parte de lo que necesita, será mucho más receptiva.

Creo que Caleb respondió bien a la pregunta de almacenamiento en caché. Iba a tocar el proceso para actualizar su UI a medida que recupera las imágenes, por ejemplo, suponiendo que tiene un NSCache para sus imágenes llamado _imageCache :

Primero, defina una propiedad de cola de operación para la vista de tabla:

 @property (nonatomic, strong) NSOperationQueue *queue; 

Luego, en viewDidLoad , inicializa esto:

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

Y luego, en cellForRowAtIndexPath , podrías:

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

Solo menciono esto ya que he visto algunos ejemplos de código flotando en SO recientemente que usan GCD para actualizar la propiedad de image UIImageView apropiada, pero en el proceso de enviar la actualización de UI a la cola principal, emplean técnicas curiosas (p. Ej. , recargando la celda o la tabla, simplemente actualizando la propiedad de la imagen del objeto cell existente devuelto en la parte superior de la tableView:cellForRowAtIndexPath (que es un problema si la fila se ha desplazado de la pantalla y la celda ha sido quitada y se está reutilizando para una nueva fila), etc.). Al usar cellForRowAtIndexPath (que no debe confundirse con tableView:cellForRowAtIndexPath ), puede determinar si la celda aún está visible y / o si se ha desplazado y se ha eliminado y reutilizado.

La solución más simple es ir con algo muy usado que ha sido sometido a pruebas de estrés.

SDWebImage es una herramienta poderosa que me ayudó a resolver un problema similar y puede instalarse fácilmente con las unidades de cocoa. En podfile:

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

Caché de configuración:

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

Imagen de caché:

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

https://github.com/rs/SDWebImage

Creo que será mejor para usted usuario algo así como DLImageLoader. Más información -> 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 } }]; 

Para la parte de la pregunta sobre imágenes incorrectas, se debe a la reutilización de las celdas. La reutilización de celdas significa que las celdas existentes, que salen de la vista (por ejemplo, las celdas que salen de la pantalla en la parte superior cuando se desplaza hacia la parte inferior son las que vuelven desde abajo). Y así obtiene imágenes incorrectas Pero una vez que aparece la celda, se ejecuta el código para recuperar la imagen correcta y obtienes las imágenes adecuadas.

Puede usar un marcador de posición en el método ‘prepareForReuse’ de la celda. Esta función se utiliza principalmente cuando necesita restablecer los valores cuando la celda se abre para su reutilización. Establecer un marcador de posición aquí se asegurará de que no obtendrá imágenes incorrectas.

Las imágenes de almacenamiento en caché se pueden hacer tan simple como esto.

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; }]; 
 ////.h file #import  @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"]];