1

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)

Community
  • 1
  • 1
HaPK
  • 33
  • 5
  • Hi compañero. To download and show images asynchronously, I use AFNetworking. Concretely AFNetworking/UIImageView+AFNetworking.h : https://github.com/AFNetworking/AFNetworking/blob/master/UIKit%2BAFNetworking/UIImageView%2BAFNetworking.h – nigelman Nov 30 '15 at 23:26

3 Answers3

1

I am using SDWebimage library to download image asynchronously. This library will cache ur images also and its very usefull for images.

https://github.com/rs/SDWebImage

I hope this will help you. If you want some code then let me know.

Ekta Padaliya
  • 5,743
  • 3
  • 39
  • 51
  • Thanks for linking to the library, ended up using it since it fit perfectly with the way the interface works – HaPK Dec 01 '15 at 17:38
0

The issue is that you have a for loop in which you create the download operations, and then immediately (before those download operations are done) iterate through the noticias updating the image.

If you're going to store the image in the Noticia object, I'd suggest you do that in the operations that you created, so as each download finishes, the respective Noticia object is updated. Make sure to dispatch that update of that model object back to the main queue (from within the operation) to avoid synchronization issues. And if you also want this to be reflected in the UI as the downloads finish, you'd also trigger that on the main queue from within the operation, too.


Having said that, I agree with nigelman and Ekta that you shouldn't be doing that, and rather using an asynchronous image retrieval mechanism such as AFNetworking or SDWebImage's UIImageView categories, and that Noticia should really only contain the URL of the image. It's better to retrieve the images not only asynchronously, but also lazily using a nice cancelable mechanism and employs cacheing and the like, such as provided by these two (amongst others) categories.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

you can download async like this:

UIView *imgv = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imgv.backgroundColor = [UIColor colorWithRed:198.0f/255.0f green:198.0f/255.0f blue:198.0f/255.0f alpha:1.0f];

UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[imgv addSubview:activityIndicator];
activityIndicator.center = CGPointMake(imgv.frame.size.width / 2, imgv.frame.size.height / 2);
[activityIndicator startAnimating];

UIImageView *img = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, imgv.frame.size.width-10, imgv.frame.size.height - 10)];
img.clipsToBounds = YES;

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
    NSString *imgurl = URL_TO_IMAGE;

    NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[imgurl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
    dispatch_async(dispatch_get_main_queue(), ^{
        UIImage *image = [UIImage imageWithData:imageData];
        img.image = image;
        [imgv addSubview:img];
        imgv.userInteractionEnabled = YES;
        [activityIndicator removeFromSuperview];
    });
});

this is code from my app..

  1. create frame to image
  2. show loader
  3. download image async
  4. remove loader, show image
Jiri Zachar
  • 487
  • 3
  • 8