I created a NSOperationQueue
to download images (from Twitter for Cell):
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSString *ImagesUrl = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
NSURL *imageurl = [NSURL URLWithString:ImagesUrl];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
if (img.size.width == 0 || [ImagesUrl isEqualToString:@"<null>"]) {
[statusCell.imageCellTL setFrame:CGRectZero];
statusCell.imageCellTL.image = [UIImage imageNamed:@"Placeholder"] ;
}else
[statusCell.imageCellTL setImage:img];
this working fine, but when it appears to move the scroll and view the images are still loading, and they are changing several times until you get a picture.
And I do not like the diagnosis Profile of Time, so I wanted to somehow make this NSOperationQueue
in Background
is also possible to show how to make a "Imagecache" no need to download an image already downloaded.
**(Status = NSDictionary of Twitter Timeline).
editing::(All Cell)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Celulatime";
UITableViewCell *Cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if ( [Cell isKindOfClass:[TimeLineCell class]] ) {
TimeLineCell *statusCell = (TimeLineCell *) Cell;
status = [self.dataSource objectAtIndex:indexPath.row];
statusCell.TextCellTL.text = [status objectForKey:@"text"];
statusCell.NomeCellTL.text = [status valueForKeyPath:@"user.name"];
statusCell.UserCellTL.text = [NSString stringWithFormat:@"@%@", [status valueForKeyPath:@"user.screen_name"]];
NSDate *created_at = [status valueForKey:@"created_at"];
if ( [created_at isKindOfClass:[NSDate class] ] ) {
NSTimeInterval timeInterval = [created_at timeIntervalSinceNow];
statusCell.timeCellTL.text = [self timeIntervalStringOf:timeInterval];
} else if ( [created_at isKindOfClass:[NSString class]] ) {
NSDate *date = [self.twitterDateFormatter dateFromString: (NSString *) created_at];
NSTimeInterval timeInterval = [date timeIntervalSinceNow];
statusCell.timeCellTL.text = [self timeIntervalStringOf:timeInterval];
}
NSString *imageUrlString = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
UIImage *imageFromCache = [self.imageCache objectForKey:imageUrlString];
if (imageFromCache) {
statusCell.imageCellTL.image = imageFromCache;
[statusCell.imageCellTL setFrame:CGRectMake(9, 6, 40, 40)];
}
else
{
statusCell.imageCellTL.image = [UIImage imageNamed:@"TweHitLogo57"];
[statusCell.imageCellTL setFrame:CGRectZero];
[self.imageluckluck addOperationWithBlock:^{
NSURL *imageurl = [NSURL URLWithString:imageUrlString];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
if (img != nil) {
[self.imageCache setObject:img forKey:imageUrlString];
// now update UI in main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
TimeLineCell *updateCell = (TimeLineCell *)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell) {
[updateCell.imageCellTL setFrame:CGRectMake(9, 6, 40, 40)];
[updateCell.imageCellTL setImage:img];
}
}];
}
}];
}
}
return Cell;
}
A couple of observations:
You should probably define a NSOperationQueue
in your class and initialize it in viewDidLoad
(as well as a NSCache
) and add operations to that queue, rather than creating a new NSOperationQueue
for every image. Also, many servers limit the number of concurrent requests they'll support from each client, so make sure to set maxConcurrentOperationCount
accordingly.
@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *imageOperationQueue;
@property (nonatomic, strong) NSCache *imageCache;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageOperationQueue = [[NSOperationQueue alloc]init];
self.imageOperationQueue.maxConcurrentOperationCount = 4;
self.imageCache = [[NSCache alloc] init];
}
// the rest of your implementation
@end
Your tableView:cellForRowAtIndexPath:
should make (a) initialize the image
before starting the asynchronous image load (so you don't see the old image from the reused cell there); and (b) make sure the cell is still visible before you update it:
NSString *imageUrlString = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
UIImage *imageFromCache = [self.imageCache objectForKey:imageUrlString];
if (imageFromCache) {
statusCell.imageCellTL.image = imageFromCache;
[statusCell.imageCellTL setFrame: ...]; // set your frame accordingly
}
else
{
statusCell.imageCellTL.image = [UIImage imageNamed:@"Placeholder"];
[statusCell.imageCellTL setFrame:CGRectZero]; // not sure if you need this line, but you had it in your original code snippet, so I include it here
[self.imageOperationQueue addOperationWithBlock:^{
NSURL *imageurl = [NSURL URLWithString:imageUrlString];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
if (img != nil) {
// update cache
[self.imageCache setObject:img forKey:imageUrlString];
// now update UI in main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// see if the cell is still visible ... it's possible the user has scrolled the cell so it's no longer visible, but the cell has been reused for another indexPath
TimeLineCell *updateCell = (TimeLineCell *)[tableView cellForRowAtIndexPath:indexPath];
// if so, update the image
if (updateCell) {
[updateCell.imageCellTL setFrame:...]; // I don't know what you want to set this to, but make sure to set it appropriately for your cell; usually I don't mess with the frame.
[updateCell.imageCellTL setImage:img];
}
}];
}
}];
}
No special handling of UIApplicationDidReceiveMemoryWarningNotification
is needed because although NSCache
does not respond to this memory warning, it does automatically evict its objects as memory becomes low.
I haven't tested the above code, but hopefully you get the idea. This is the typical pattern. Your original code had a check for [ImagesUrl isEqualToString:@"<null>"]
, which I don't see how could ever be the case, but if you need some additional logic besides just my if (img != nil) ...
, then adjust that line accordingly.