Imágenes de carga diferida personalizadas de Objective C Celda UITableView

First time loading remote images into an iPhone app, and would like some help optimizing the process. What I've currently done is get the image if it doesn't exist, and cache it. The major goals are to:

  • only load images when needed.
  • save images for future use to reduce data consumption, and allow the user to have a somewhat functional app when not connected to the internet.

I just don't think I'm doing it well enough.

Here's a snippet of the code within tableView:cellForRowAtIndexPath:

MVImageCell * cell = (MVImageCell *)[tableView dequeueReusableCellWithIdentifier:@"PicsAndVideosCell"];

// empty cell
cell.imageView.image = nil;
cell.textLabel.text = nil;
cell.detailTextLabel.text = nil;

// set cell properties
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 2;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.frame = CGRectMake(15, 6, 58, 58);
cell.imageView.layer.cornerRadius = 6;
cell.imageView.layer.masksToBounds = YES;

Photoset * sfc = [self.myarray objectAtIndex:indexPath.row];
cell.cid = sfc.sfcid;
cell.ctitle = sfc.title;
cell.cimg = sfc.cover;

cell.textLabel.text = sfc.title;
cell.detailTextLabel.text = sfc.date;

// set cell image
MVImage * thumb = [[MVImage alloc] init];
NSString * retina = ([[MVProject sharedInstance] settings_retina]) ? @"2" : @"";
if ([NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]]]) {
    thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]]];

    [cell.imageView setImage:[MVImage imageWithImage:[[UIImage alloc] initWithData:thumb.data] covertToWidth:58.0f covertToHeight:58.0f]];
    [cell bringSubviewToFront:[cell.imageView superview]];
} else {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    dispatch_async(queue, ^{
        dispatch_sync(dispatch_get_main_queue(), ^{
            thumb.data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@", cell.cimg]]];
            thumb.title = cell.ctitle;
            [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:thumb] forKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]];

            [cell.imageView setImage:[MVImage imageWithImage:[[UIImage alloc] initWithData:thumb.data] covertToWidth:58.0f covertToHeight:58.0f]];
            [cell bringSubviewToFront:[cell.imageView superview]];
        });
    });
}

return cell;

Should I use a SQLite database instead of NSUserDefaults?

I'm also having trouble with the asynchronous loading. I feel like it's supposed to look like this:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
    thumb.data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@", cell.cimg]]];
    thumb.title = cell.ctitle;
    [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:thumb] forKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]];

    dispatch_sync(dispatch_get_main_queue(), ^{
        [cell.imageView setImage:[MVImage imageWithImage:[[UIImage alloc] initWithData:thumb.data] covertToWidth:58.0f covertToHeight:58.0f]];
        [cell bringSubviewToFront:[cell.imageView superview]];
    });
});

But that obviously saves the wrong image data to the NSUserDefault destination.

Any help on this, pointers on my coding style, and anything else is greatly appreciated.

¡Gracias!

preguntado el 31 de enero de 12 a las 16:01

2 Respuestas

Just having a quick look at your code - you seem to be pushing blocks onto asynchronous queues, but you are calling UI code in those blocks.

You should only run UI code on the main thread.

As for a solution - have a look at some of the open source implementations to either give you an idea of what you should be doing, or just use them directly.

Uno de ellos es AsyncImageView en Github.

There are others that a quick search will bring up.

Respondido el 31 de enero de 12 a las 20:01

2 suggestions: 1. Don't store images in NSUserDefaults, that is more suitable for user preferences, like strings.

  1. Don't do the unarchive operation twice. Just do it, then check the results.

Reemplazar esto:

if ([NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]]]) {
thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]]];

Con este:

thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]]];
 if ( thumb ) ... 

Respondido el 31 de enero de 12 a las 20:01

se if (thumb = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"Settings_SFCCovers%@_%@", retina, cell.cid]]]) {...} volver verdadero? - Matisse VerDuyn

It would if the unarchive operation succeeded, so yes, that form works just as well. - Rayfleck

Unfortunately, neither of these solutions are correct. They always return false. - Matisse VerDuyn

setObject:[NSKeyedArchiver archivedDataWithRootObject:thumb] - are you sure this is returning a non-null object? Also, if you persist items in NSUserDefaults, you should call [[NSUserDefaults standardDefaults] synchronize] - Rayfleck

the original way works correctly, as the data is stored, and the images are loaded (thus are non-null). i don't think it's possible to call a synchronized method within an asynchronous method, and placing [[NSUserDefaults standardUserDefaults] synchronize] dentro de dispatch_sync(dispatch_get_main_queue(), ^{}); is keeping the data from being saved. - Matisse VerDuyn

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.