I'm optimizing the download and show process for an iOS app. In this app I have a Noticia object that contains, among other things, an Image URL address (stored as a string), and a UIImage object with the actual image.
Right now everything is done synchronous, and it opens a connection for each image that it downloads like so:
NSURL *imageURL = [NSURL URLWithString:noti.urlImagen];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
So, when there are a lot of news to download, it takes a lot of time to download and the app is absolutely unresponsive while that happens.
So I'm changing it right now, I can now download asynchronously all the images, with the code found in this question, but now I need to show them as soon as they download in the list of news. How could I do this?.
The noticia object Noticia.h
#import <UIKit/UIKit.h>
@interface Noticia : NSObject
@property (nonatomic) int identificacion;
@property (nonatomic, strong) NSString *titulo;
@property (nonatomic, strong) NSString *descripcion;
@property (nonatomic, strong) NSString *fecha;
@property (nonatomic, strong) NSString *urlImagen;
@property (nonatomic, strong) NSString *urlNoticia;
@property (nonatomic, strong) NSString *categoria;
@property (nonatomic, strong) NSString *fuente;
@property (nonatomic, strong) NSString *contenido;
@property (nonatomic) int tipo;
@property (nonatomic) UIImage *imagen;
-(id)initWithTitulo:(NSString *)ti descripcion:(NSString *)de fecha:(NSString *)fe urlImagen:(NSString *)uri urlNoticia:(NSString *)urn categoria:(NSString *)ca fuente:(NSString *)fu contenido:(NSString *)co tipo:(int)tip;
@end
Noticia.m
#import "Noticia.h"
@implementation Noticia
@synthesize titulo, descripcion, urlImagen, urlNoticia, categoria, fuente, contenido, tipo, fecha;
-(id)initWithTitulo:(NSString *)ti descripcion:(NSString *)de fecha:(NSString *)fe urlImagen:(NSString *)uri urlNoticia:(NSString *)urn categoria:(NSString *)ca fuente:(NSString *)fu contenido:(NSString *)co tipo:(int)tip{
if(self = [super init]){
self.titulo = ti;
self.descripcion = de;
self.fecha = fe;
self.urlImagen = uri;
self.urlNoticia = urn;
self.categoria = ca;
self.fuente = fu;
self.contenido = co;
self.tipo = tip;
}
return self;
}
-(NSString *)description{
return [NSString stringWithFormat:@"Noticia:\nTitulo:%@\nDescripcion:%@\nFecha:%@\nurlImagen:%@\nurlNoticia:%@\nCategoria:%@\nFuente:%@\nContenido:%@\nTipo:%i",titulo, descripcion, fecha, urlImagen, urlNoticia, categoria, fuente, contenido, tipo];
}
@end
How the Noticia gets downloaded (the async way)
-(NSMutableArray *)traerNoticias{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:kEndPointNews]];
if (data != NULL) {
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *arregloNoticias = [[NSMutableArray alloc]init];
for (NSDictionary *noticia in json) {
NSDictionary *noticiaInfo = [noticia objectForKey:@"noticia"];
Noticia *noti = [[Noticia alloc]initWithTitulo:[noticiaInfo objectForKey:@"titulo"] descripcion:[noticiaInfo objectForKey:@"descripcion"] fecha:[noticiaInfo objectForKey:@"fecha"] urlImagen:[noticiaInfo objectForKey:@"urlImagen"] urlNoticia:[noticiaInfo objectForKey:@"urlNoticia"] categoria:@"a" fuente:@"a" contenido:[noticiaInfo objectForKey:@"contenido"] tipo:[[noticiaInfo objectForKey:@"destacada"]intValue]];
noti.identificacion = [[noticiaInfo objectForKey:@"id"]intValue];
NSLog(@"%@", noti);
//NSURL *imageURL = [NSURL URLWithString:noti.urlImagen];
//NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
NSData *imageData = nil;
noti.imagen = [UIImage imageWithData:imageData];
[arregloNoticias addObject:noti];
}
arregloNoticias = [self cargarImagenesAsincrono:arregloNoticias];
return arregloNoticias;
}else{
return nil;
}
}
-(NSMutableArray *)cargarImagenesAsincrono:(NSMutableArray *)arregloNoticias
{
NSMutableArray *urlsNoticias = [[NSMutableArray alloc]init];
for (Noticia *Noticias in arregloNoticias) {
NSURL *url = [NSURL URLWithString:Noticias.urlImagen];
[urlsNoticias addObject:url];
}
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 7;
NSMutableArray *imagenes = [[NSMutableArray alloc]init];
for (NSURL* url in urlsNoticias)
{
[queue addOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:url];
[imagenes addObject:data];
}];
}
int i=0;
for (NSData *imageData in imagenes) {
Noticia *noti=[arregloNoticias objectAtIndex:i];
noti.imagen=[UIImage imageWithData:imageData];
[arregloNoticias replaceObjectAtIndex:i withObject:noti];
i++;
}
return arregloNoticias;
}
The main interface expects to already have an UImage object ready in the Noticia object to display it. I've also seen that the last for statement in cargarImagenesAsincrono never happens, so the original array with Noticia objects never really gets updated with the images.
How can I achieve this? The main interface is in another file.
I can post more code if you need it. (sorry for the spanish)