Caching with UIImage and downloaded images - ios

I have a class method which fetches images with a completion block. This fetched UIImage is added to an NSCache with a relevant key. This seems to work as expected, however, in the method which fetches images I am using a UIImage's imageWithData: method, which I have discovered does not cache it's data, only imageNamed: does.
I am understandably getting memory warnings because of this, how do I make sure the images loaded with UIImage's imageWithData: method are removed from memory when not needed anymore?
EDIT
Here is the code for the method which downloads the images.
- (void)imageForFootageSize:(FootageSize)footageSize withCompletionHandler:(void (^)(UIImage *image))completionBlock
{
if (completionBlock) {
__block UIImage *image;
// Try getting local image from disk.
//
__block NSURL *imageURL = [self localURLForFootageSize:footageSize];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
if (image) {
completionBlock(image);
} else {
//
// Otherwise try getting remote image.
//
imageURL = [self remoteURLForFootageSize:footageSize];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
image = [UIImage imageWithData:imageData];
if (image) {
//
// Save remote image to disk
//
NSURL *photoDirectoryURL = [Footage localURLForDirectory];
// Create the folder(s) where the photos are stored.
//
[[NSFileManager defaultManager] createDirectoryAtPath:[photoDirectoryURL path] withIntermediateDirectories:YES attributes:nil error:nil];
// Save photo
//
NSString *localPath = [[self localURLForFootageSize:footageSize] path];
[imageData writeToFile:localPath atomically:YES];
}
completionBlock(image);
});
});
}
});
});
}
}
EDIT 2
Methods which use the above class method to fetch and process the UIImage in the completionHandler.
Method inside UICollectionViewCell subclass.
- (void)setPhoto:(Photo *)photo withImage:(UIImage *)image
{
[self setBackgroundColor:[UIColor blackColor]];
[self.imageView setBackgroundColor:[UIColor clearColor]];
if (photo && !image) {
[photo imageForFootageSize:[Footage footageSizeThatBestFitsRect:self.bounds]
withCompletionHandler:^(UIImage *image) {
if ([self.delegate respondsToSelector:#selector(galleryPhotoCollectionViewCell:didLoadImage:)]) {
[self.delegate galleryPhotoCollectionViewCell:self didLoadImage:image];
}
image = nil;
}];
}
[self.imageView setImage:image];
BOOL isPhotoAvailable = (BOOL)(image);
[self.imageView setHidden:!isPhotoAvailable];
[self.activityIndicatorView setHidden:isPhotoAvailable];
}
Method in UICollectionView data source delegate
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DIGalleryPhotoCollectionViewCell *photoCell = [collectionView dequeueReusableCellWithReuseIdentifier:photoCellIdentifier forIndexPath:indexPath];
[photoCell setDelegate:self];
Footage *footage = [self footageForIndexPath:indexPath];
Photo *photo = ([footage isKindOfClass:[Photo class]]) ? (Photo *)footage : nil;
if (photo) {
//
// Photo
//
[photoCell setPhoto:photo withImage:[self.galleryCache objectForKey:photo.footageID]];
}
return photoCell;
}
Here are the other relevant methods:
- (void)galleryPhotoCollectionViewCell:(DIGalleryPhotoCollectionViewCell *)cell didLoadImage:(UIImage *)image
{
NSIndexPath *indexPath = [self.galleryCollectionView indexPathForCell:cell];
Footage *footage = [self footageForIndexPath:indexPath];
if ([footage isKindOfClass:[Footage class]]) {
Photo *photo = (Photo *)footage;
UIImage *cachedImage = [self.galleryCache objectForKey:photo.footageID];
if (!cachedImage) {
cachedImage = image;
[self.galleryCache setObject:image forKey:photo.footageID];
}
[cell setPhoto:photo withImage:image];
}
}
And also my getter method for the NSCache property galleryCache
- (NSCache *)galleryCache
{
if (!_galleryCache) {
_galleryCache = [[NSCache alloc] init];
}
return _galleryCache;
}

Instead of rolling your own image downloading and caching solution you might be better off using SDWebImage. Then you don't have to worry about the downloading, caching or anything. SDWebImage also using disk caching so you don't have to worry about freeing memory.
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// do something with image
}
}];
I'm not sure but you also might have a retain cycle:
__weak typeof(self) weakSelf = self;
[photo imageForFootageSize:[Footage footageSizeThatBestFitsRect:self.bounds] withCompletionHandler:^(UIImage *image) {
if ([weakSelf.delegate respondsToSelector:#selector(galleryPhotoCollectionViewCell:didLoadImage:)])
{
[weakSelf.delegate galleryPhotoCollectionViewCell:weakSelf didLoadImage:image];
}
image = nil;
}];

Related

NSLog not called within block

my replaceObjectatIndex:withObject: is not being called when I put it inside a block. I know this because when I NSLog in the outer block the value doesn't change. why is the method inside the inner block not being called while the method in the outer block does? what's the difference?
this is the code:
if (cell.selected) {
[[SDImageCache sharedImageCache] queryDiskCacheForKey:imageID
done:^(UIImage *image, SDImageCacheType cacheType)
{
// image is not nil if image was found
if (image == nil) {
//image is not found
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:[NSURL URLWithString:link]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
{
if (image && finished)
{
// image is finished being downloaded
// resize image
UIImage *resizedImage = [self imageWithImage:image forRowAtIndexPath:indexPath];
// store resized image in cache
[[SDImageCache sharedImageCache] storeImage:resizedImage forKey:imageID];
//set image view to resized image
[textCell.testImage setImage:resizedImage];
[self.heightArray replaceObjectAtIndex:indexPath.row
withObject:[NSNumber numberWithFloat:image.size.height]];
}
//delete original sized image
image = nil;
}];
} else {
//image is found
[textCell.testImage setImage:image];
NSLog(#"image found %#", [self.heightArray objectAtIndex:indexPath.row]);
}
}];
} else {
//cell is not selected
textCell.testImage.image = nil;
}
By the way, the setImage: method works perfectly but not replaceObjectatIndex:withObject:
I don't know what you are doing but I found an issue in your code: you are using the image even after you set it to nil.
Correction:
//delete original sized image -
//??image = nil;
[self.heightArray replaceObjectAtIndex:indexPath.row
withObject:[NSNumber numberWithFloat:image.size.height]];
image = nil;

SDWebImage repeating images in cell instead of waiting to load.

I am using SDWebImage for fetching images from server to my table view app in IOS.
But the problem is that when I scroll down in table view instead of waiting for the images to load it put the images downloaded in the first few rows of table view and repeat those images till the end row and when it downloads the images it changes those repeated images to the actual image for that row.
NSURL * url = [NSURL URLWithString:string];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:url
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished,NSURL * url)
{
if (finished && image )
{
NSArray *visibleIndexPaths = [tableView indexPathsForVisibleRows];
if ([visibleIndexPaths containsObject:indexPath]) {
cell.myImage.image = image;
}
}
}];
Actually, it is not a bug with SDWebImage, but rather it's the nature of how UITableView works. downloadImageWithURL, is an async process,so when your tableView delegate/datasource methods are called, the image isn't downloaded yet, therefore cellForRow doesn't have an image to display.
To overcome this issue you should first check image from cache as
[[SDWebImageManager sharedManager] diskImageExistsForURL:[NSURL URLWithString:ImageUrl]]
if yes then set image to UIImageView otherwise use downloadImageWithURL to download image and add cell tag(To display image to correct row) as
cell.tag = indexPath.row;
on successfull download first check correct row as
if(cell.tag == indexPath.row){
and set image to UIImageView.Here is setImage method.
-(void)setImage:(SLFirstTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath{
SLFirstTableViewCellItem * slFirstTableViewCellItem = [self.categories objectAtIndex:indexPath.row]; // categories is array of items,replace with yours.
NSString *ImageUrl = slFirstTableViewCellItem.imageUrl; //assume image url is in slFirstTableViewCellItem object.
cell.tag = indexPath.row;
if([[SDWebImageManager sharedManager] diskImageExistsForURL:[NSURL URLWithString:ImageUrl]]){
[cell.imgItem setImage: [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:ImageUrl]];
[self hideProgressView:cell];
}else{
[self showProgressView:cell];
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:[NSURL URLWithString:ImageUrl]
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
{
if (image && finished)
{
[[SDImageCache sharedImageCache] storeImage:image forKey:ImageUrl]; // cache image
if(cell.tag == indexPath.row){ // check if correct row
[cell.imgItem setImage:image];
[self hideProgressView:cell];
}
}else{
cell.imgItem.hidden = YES;
cell.progressBar.hidden = YES;
}
}];
}
}
And define showProgressView and hideProgressView methods as
-(void)showProgressView:(SLFirstTableViewCell *)cell {
cell.progressText.hidden = NO;
cell.progressBar.hidden = NO;
cell.imgItem.hidden = YES;
[cell.progressBar startAnimating];
[cell.progressText setText:#"Loading Image..."];
}
-(void)hideProgressView:(SLFirstTableViewCell *)cell{
cell.progressBar.hidden = YES;
cell.progressText.hidden = YES;
cell.imgItem.hidden = NO;
[cell.progressBar stopAnimating];
}
finally call setImage from cellForRowAtIndexPath method(before returning cell) as
[self setImage:cell atIndexPath:indexPath];

Multitasking with download images in iOS sdk

I have a UItableView that is listing number of outlets respectively, each outlet has a logo image. I save those images locally on iPhone, so if any image is found on iPhone it will fetch from there if not then it will send a service call and fetch data. It is working accordingly but when I scroll down on tableview it gets hanged at certain point when the image is being downloaded, as it is downloaded it works fine again. Is there any solution where I can perform this multitasking of downloading images along with representing them either from service call or fetching it locally.
Here is my code..
// downloading images of outlets locally
NSLog(#"Downloading...");
NSString *imageLink = [NSString stringWithFormat:#"http://cf.abc.pk/outlets/l/%#",outs.logo];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageLink]]];
NSLog(#"%f,%f",image.size.width,image.size.height);
NSString *docDir =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSLog(#"%#",docDir);
NSLog(#"saving png");
NSString *pngFilePath = [NSString stringWithFormat:#"%#/%d.png",docDir,[[arrOutletIds objectAtIndex:webserviceCall] intValue]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pngFilePath];
if(!fileExists)
{
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(image)];
[data1 writeToFile:pngFilePath atomically:YES];
}
Let's try:
//When you download something, execute in background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
//download some thing
dispatch_async( dispatch_get_main_queue(), ^{
// when download finish execute in main thread to update data
// this function should be called in response of downloading
});
});
Hope this helps you.
Download image in background will solve your problem here
Try this.
if(!fileExists)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(image)];
[data1 writeToFile:pngFilePath atomically:YES];
});
}
You can use this code which integrates ImageCache to download async and cache images. This part of code shows downloading images in UITableView. Or you can check out this project on GitHub SDImageCache
if ([[ImageCache sharedImageCache] DoesExist:photoURL] == true)
{
image = [[ImageCache sharedImageCache] GetImage:photoURL];
cell.imageView.image = image;
}
else
{
cell.imageView.image = [UIImage imageNamed:#"PlaceHolder"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:photoURL]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
if (cell.tag == indexPath.section) {
cell.imageView.image = image;
[cell setNeedsLayout];
[[ImageCache sharedImageCache] AddImage:image imageURL:photoURL];
}
});
}
});
}
Import these files to your project
ImageCache.h
#import <Foundation/Foundation.h>
#interface ImageCache : NSObject
#property (nonatomic, retain) NSCache *imgCache;
#pragma mark - Methods
+ (ImageCache*)sharedImageCache;
- (void) AddImage:(UIImage *)image imageURL:(NSString *)imageURL;
- (UIImage*) GetImage:(NSString *)imageURL;
- (BOOL) DoesExist:(NSString *)imageURL;
#end
ImageCache.m
#import "ImageCache.h"
#implementation ImageCache
#synthesize imgCache;
#pragma mark - Methods
static ImageCache* sharedImageCache = nil;
+(ImageCache*)sharedImageCache
{
#synchronized([ImageCache class])
{
if (!sharedImageCache)
sharedImageCache= [[self alloc] init];
return sharedImageCache;
}
return nil;
}
+(id)alloc
{
#synchronized([ImageCache class])
{
NSAssert(sharedImageCache == nil, #"Attempted to allocate a second instance of a singleton.");
sharedImageCache = [super alloc];
return sharedImageCache;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
imgCache = [[NSCache alloc] init];
}
return self;
}
- (void) AddImage:(UIImage *)image imageURL:(NSString *)imageURL
{
if (image==nil) {
}
else
{
[imgCache setObject:image forKey:imageURL];
}
}
- (NSString*) GetImage:(NSString *)imageURL
{
return [imgCache objectForKey:imageURL];
}
- (BOOL) DoesExist:(NSString *)imageURL
{
if ([imgCache objectForKey:imageURL] == nil)
{
return false;
}
return true;
}
#end

NSCache holds strong pointer to UIImage instantiated with imageWithData: and does not remove from memory on unload

I have a View Controller with a property galleryCache and when an image is downloaded using GCD and imageWithData: the image is added to the cache successfully with a key. However, when the view controller is dismissed it keeps strong pointers to those downloaded images causing them not to be removed from memory. Even if I use the removeAllObjects method on the cache in viewDidDisappear: memory does not clear up.
Does anyone know why this might be?
Here is the code for the method which downloads the images.
- (void)imageForFootageSize:(FootageSize)footageSize withCompletionHandler:(void (^)(UIImage *image))completionBlock
{
if (completionBlock) {
__block UIImage *image;
// Try getting local image from disk.
//
__block NSURL *imageURL = [self localURLForFootageSize:footageSize];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
if (image) {
completionBlock(image);
} else {
//
// Otherwise try getting remote image.
//
imageURL = [self remoteURLForFootageSize:footageSize];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
image = [UIImage imageWithData:imageData];
if (image) {
//
// Save remote image to disk
//
NSURL *photoDirectoryURL = [Footage localURLForDirectory];
// Create the folder(s) where the photos are stored.
//
[[NSFileManager defaultManager] createDirectoryAtPath:[photoDirectoryURL path] withIntermediateDirectories:YES attributes:nil error:nil];
// Save photo
//
NSString *localPath = [[self localURLForFootageSize:footageSize] path];
[imageData writeToFile:localPath atomically:YES];
}
completionBlock(image);
});
});
}
});
});
}
}
Methods which use the above class method to fetch and process the UIImage in the completionHandler.
Method inside UICollectionViewCell subclass.
- (void)setPhoto:(Photo *)photo withImage:(UIImage *)image
{
[self setBackgroundColor:[UIColor blackColor]];
[self.imageView setBackgroundColor:[UIColor clearColor]];
if (photo && !image) {
[photo imageForFootageSize:[Footage footageSizeThatBestFitsRect:self.bounds]
withCompletionHandler:^(UIImage *image) {
if ([self.delegate respondsToSelector:#selector(galleryPhotoCollectionViewCell:didLoadImage:)]) {
[self.delegate galleryPhotoCollectionViewCell:self didLoadImage:image];
}
image = nil;
}];
}
[self.imageView setImage:image];
BOOL isPhotoAvailable = (BOOL)(image);
[self.imageView setHidden:!isPhotoAvailable];
[self.activityIndicatorView setHidden:isPhotoAvailable];
}
Method in UICollectionView data source delegate
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DIGalleryPhotoCollectionViewCell *photoCell = [collectionView dequeueReusableCellWithReuseIdentifier:photoCellIdentifier forIndexPath:indexPath];
[photoCell setDelegate:self];
Footage *footage = [self footageForIndexPath:indexPath];
Photo *photo = ([footage isKindOfClass:[Photo class]]) ? (Photo *)footage : nil;
if (photo) {
//
// Photo
//
[photoCell setPhoto:photo withImage:[self.galleryCache objectForKey:photo.footageID]];
}
return photoCell;
}
Here are the other relevant methods:
- (void)galleryPhotoCollectionViewCell:(DIGalleryPhotoCollectionViewCell *)cell didLoadImage:(UIImage *)image
{
NSIndexPath *indexPath = [self.galleryCollectionView indexPathForCell:cell];
Footage *footage = [self footageForIndexPath:indexPath];
if ([footage isKindOfClass:[Footage class]]) {
Photo *photo = (Photo *)footage;
UIImage *cachedImage = [self.galleryCache objectForKey:photo.footageID];
if (!cachedImage) {
cachedImage = image;
[self.galleryCache setObject:image forKey:photo.footageID];
}
[cell setPhoto:photo withImage:image];
}
}
And also my getter method for the NSCache property galleryCache
- (NSCache *)galleryCache
{
if (!_galleryCache) {
_galleryCache = [[NSCache alloc] init];
}
return _galleryCache;
}
EDIT
Here is a snapshot of Instruments showing the retain count history of one of the NSCache once its owner (a View Controller) is dismissed.
I'm not seeing anything obvious here, though I'd suggest putting a breakpoint where you purge the cache and make sure that's actually happening like you think it is.
If you still don't find it, you can run Allocations tool in Instruments and turn on "record reference counts" (see latter part of this answer, iOS app with ARC, find who is owner of an object), and you can find out precisely where your lingering strong reference is, at which point you can tackle the remediation.
The other obvious solution is to eliminate all of this code and use a proven image caching tool, like SDWebImage which does a lot of the memory and persistent storage caching for you. It's a pretty decent implementation.
OK, so after re examining my own code and re examining properties for the billionth x n time, it turns out my error was assigning the delegate property as a 'strong' type. Lesson learned: ALWAYS set delegates as WEAK.
I will definitely have to learn more about Instruments, however.

Is there a way to load a text string first and then the image using AFNetworking?

I'm using AFNetworking to parse JSON to my app (using Rails as my backend). Right now my app is very slow so I'm trying to figure out a way to make it smoother. When I first load the app it takes a few seconds for it to populate (it shows the Nav items and a white page, then a few seconds later my "posts" appear).
Collection View Controller
- (void)viewDidLoad
{
[super viewDidLoad];
self.upcomingReleases = [[NSMutableArray alloc] init];
[self makeReleasesRequests];
[self.collectionView registerClass:[ReleaseCell class] forCellWithReuseIdentifier:#"ReleaseCell"];
}
-(void)makeReleasesRequests
{
NSURL *url = [NSURL URLWithString:#"http://www.soleresource.com/upcoming.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"#");
self.upcomingReleases = [responseObject objectForKey:#"upcoming_releases"];
[self.collectionView reloadData];
} failure:nil];
[operation start];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.upcomingReleases count];
}
#pragma mark - Show upcoming release shoe
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
ReleaseCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
NSDictionary *upcomingReleaseDictionary = [self.upcomingReleases objectAtIndex:indexPath.row];
NSString *thumbURL = nil;
cell.release_name.text = [NSString stringWithFormat:#"%# — $%#",[upcomingReleaseDictionary objectForKey:#"release_name"], [upcomingReleaseDictionary objectForKey:#"release_price"]];
if ([upcomingReleaseDictionary[#"images"] isKindOfClass:[NSArray class]] && [upcomingReleaseDictionary[#"images"] count]) {
thumbURL = upcomingReleaseDictionary[#"images"][0][#"image_file"][#"image_file"][#"thumb"][#"url"];
if (thumbURL)
{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbURL]];
UIImage *image = [UIImage imageWithData:imageData];
cell.thumb.image = image;
}
}
else {
cell.thumb.image = [UIImage imageNamed:#"air-jordan-5-fear.png"];
}
return cell;
}
Each of my posts has a text string and a image. Is there a way to load the text so that it appears right away and then load my image? Or is there another way to speed up my app load speed (Maybe loadin a certain of posts first and then loading the rest - the ones that the user cant see until they scroll down).
Thanks.
You should load your image lazily and asynchronously (DON'T block main thread) when coming from server. (AFNetworking already has caching category method on UIImageView. (Check out this for more)
if (thumbURL)
{
[cell.thumb setImageWithURL:[NSURL URLWithString:thumbURL] placeholderImage:[UIImage imageNamed:#"air-jordan-5-fear.png"]];
}
EDIT -
Ensure to pull UIKit+AFNetworking folder into your project and #import "UIKit+AFNetworking.h" into your .m file. The link to download complete AFNetworking can be found here and documentation specific to this question here.
Your problem is this:
if (thumbURL)
{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbURL]];
UIImage *image = [UIImage imageWithData:imageData];
cell.thumb.image = image;
}
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbURL]];
UIImage *image = [UIImage imageWithData:imageData];
You should never be getting data in cellForItemAtIndexPath:. You should only be displaying what you have already. Your code makes it so no cell is returned until a thumbnail is downloaded. You can measure this using the Time Profiler in Instruments.
I'm assuming thumb is a UIImageView. Try this:
if (thumb) {
[thumb setImageWithURL:[NSURL URLWithString:#"http://i.imgur.com/r4uwx.jpg"]];
}
This method, also included with AFNetworking, will download the image, and update it in the cell once it's done downloading. Documentation and other similar methods are here.

Resources