Download image asynchronously - iphone

I need to download an image from the web and display it in an ImageView. Presently I am using SDWebImage (It is an asynchronous image downloader with cache support, with a UIImageView category).
But it crashes when I click the back button and forward button (when I try to go back and forth of the view repeatedly). Anyway this happens very rarely, but I need to get rid of this bug. Is there any other library (that does not use private API's) that I could use in my project?

Yes. You can user other libary. I've already implemented that using AsyncImageView which is inherited from UIImageView. What it does is it stores images in Cache memory fetched from a url and whenever you need to load image from the same URL again it will simply load it from the cache memory saving a lot of time.
Just follow the link for implementing that:
https://github.com/nicklockwood/AsyncImageView#readme
http://www.markj.net/iphone-asynchronous-table-image/
Please have a look at the image showing the technique I've implemented. It lets you do other activity while images are loading.:

NSURLConnection provides asynchronous downloading and is built into iOS.

I think that the bug that you describe may occur because when you "go back" release some objects that can be delegates of connections that are still running. For avoid crashes, you should cancel the connections before release or dealloc any object that could be a delegate of a running connection.
Another alternative for image async download is http://allseeing-i.com/ASIHTTPRequest/ .

I personally use the built in Grand Central Dispatch feature in iOS to download images from the server asynchronously.
Below is a code I used to fetch photos from Flickr in one of my apps.
In your image/photo class, have a function that is something like this:
- (void)processImageDataWithBlock:(void (^)(NSData *imageData))processImage
{
NSString *url = self.imageURL;
dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Photo Downloader", NULL);
dispatch_async(downloadQueue, ^{
NSData *imageData = *insert code that fetches photo from server*;
dispatch_async(callerQueue, ^{
processImage(imageData);
});
});
dispatch_release(downloadQueue);
}
In your Photo View Controller, you can call this function like this:
- (void)viewWillAppear:(BOOL)animated
{
[spinner startAnimating];
[self.photo processImageDataWithBlock:^(NSData *imageData) {
if (self.view.window) {
UIImage *image = [UIImage imageWithData:imageData];
imageView.image = image;
imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
scrollView.contentSize = image.size;
[spinner stopAnimating];
}
}];
}

Check out EGOImageLoading by enormego for caching images.
It Works just like UIImageView and lets you download images from HTTP asynchronously and also its easy to integrate

//JImage.h
#import <Foundation/Foundation.h>
#interface JImage : UIImageView {
NSURLConnection *connection;
NSMutableData* data;
UIActivityIndicatorView *ai;
}
-(void)initWithImageAtURL:(NSURL*)url;
#property (nonatomic, retain) NSURLConnection *connection;
#property (nonatomic, retain) NSMutableData* data;
#property (nonatomic, retain) UIActivityIndicatorView *ai;
#end
//JImage.m
#import "JImage.h"
#implementation JImage
#synthesize ai,connection, data;
-(void)initWithImageAtURL:(NSURL*)url {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self setContentMode:UIViewContentModeScaleToFill];
if (!ai){
[self setAi:[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]];
[ai startAnimating];
[ai setFrame:CGRectMake(27.5, 27.5, 20, 20)];
[ai setColor:[UIColor blackColor]];
[self addSubview:ai];
}
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil) data = [[NSMutableData alloc] initWithCapacity:5000];
[data appendData:incrementalData];
NSNumber *resourceLength = [NSNumber numberWithUnsignedInteger:[data length]];
NSLog(#"resourceData length: %d", [resourceLength intValue]);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Connection error...");
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[ai removeFromSuperview];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self setImage:[UIImage imageWithData: data]];
[ai removeFromSuperview];
}
#end
//Include the definition in your class where you want to use the image
-(UIImageView*)downloadImage:(NSURL*)url:(CGRect)frame {
JImage *photoImage=[[JImage alloc] init];
photoImage.backgroundColor = [UIColor clearColor];
[photoImage setFrame:frame];
[photoImage setContentMode:UIViewContentModeScaleToFill];
[photoImage initWithImageAtURL:url];
return photoImage;
}
//How to call the class
UIImageView *imagV=[self downloadImage:url :rect];
//you can call the downloadImage function in looping statement and subview the returned imageview.
//it will help you in lazy loading of images.
//Hope this will help

I know this is a very old thread but recently i had a lot of random crashes with SDWebImage, so i had to implement my own lazy loading and caching mechanism. It works pretty well, i just haven't tested it in heavy load cases. So here is the .h and .m files followed by the way i use it :
// UIImageView+CustomCache.h
#interface UIImageView(CustomCache)
-(void)startAsyncDownload:(UIImage*)placeHolderImage imageUrlString:(NSString*)imageUrlString;
#end
// UIImageView+CustomCache.m
#import "UIImageView+CustomCache.h"
#implementation UIImageView(CustomCache)
-(void)startAsyncDownload:(UIImage*)placeHolderImage imageUrlString:(NSString*)imageUrlString{
self.image = placeHolderImage;
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:
[NSURL URLWithString:imageUrlString]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionHandler){
#autoreleasepool {
if (connectionHandler != nil) {
NSLog(#"error in downloading description %#",connectionHandler.localizedDescription);
} else {
ImagesCacheHandler *ref = [ImagesCacheHandler new];
UIImage *imageFromData = [[UIImage alloc] initWithData:data];
if (imageFromData != NULL && imageFromData != nil && data.length > 0) {
self.image = imageFromData;
//custom store to sqlite
[ref archiveImage:imageUrlString imageData:data moc:[ref fetchContext]];
}
}
}
}];
}
#end
And in my table view i use (i import of course UIImageView+CustomCache.h)
UIImageView *imageViewToLazyLoad = (UIImageView*)[cell viewWithTag:1];
[imageViewToLazyLoad startAsyncDownload:[UIImage imageNamed#"palce_Holder_Image_name"] imageUrlString:imageUrl];

I personally prefer using NSURLConnection sendSynchronousRequest and putting a GCD wrapper around it. Keeps everything neat and tidy.

Related

iphone memory issue while downloading images

I have a wierd problem. The requirement is to download image from a url on swipe and display it in image view .It is working all fine but I am getting memory warnings after 30 images and after few more swipe app crashes.
Implementation is pretty straight forward,but already spent almost 2 days to figure out the issue.
On each swipe i am calling A method :-
-(void)callDownloadImageAPI{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
[self loadIndicator:#"Please Wait.!!" :#"Please be patient while we are downloading image for you"];
#try{
DownloadImage *downLoadImge =[[DownloadImage alloc] init];
downLoadImge.delegate=self;
[downLoadImge getImage:[self.allUrlArray objectAtIndex:self.initialImageViewCounter]];
}
#catch (NSException *e) {
NSLog(#"callDownloadImageAPI exception %#",[e description]);
[HUD hide:YES];
}
[pool release];
}
This method download 1 image at a time and send UIImage to its delegate
//Implementation of DownloadImage.h and .m
#protocol DownloadImageDelegate
#required
- (void)messageFormServerWithImage:(UIImage*)imageFromSever;
- (void)gettingImageFailed :(NSString*)errorDesc;
#end
#interface DownloadImage : NSObject
#property(strong) NSURLConnection* connection;
#property(weak) id<DownloadImageDelegate> delegate;
#property(strong) NSMutableData* data;
-(void)getImage:(NSString *)imageUrl;
//DownloadImage.m
-(void)getImage:(NSString *)imageUrl{
#autoreleasepool {
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {
#autoreleasepool {
NSLog(#"connectionDidFinishLoading");
self.connection=nil;
if( self.data == nil) {
return;
}
// NSString* jsonStr = [[NSString alloc] initWithData:self.data encoding:NSASCIIStringEncoding];
UIImage *img=[UIImage imageWithData:self.data];
// NSArray *messages_json = [parser objectWithString:jsonStr error:nil];
[self.delegate messageFormServerWithImage:img];
self.data = nil;
img= nil;
}
}
Other delegates of NSUrlConnections are implemented but I am not putting it here .
Once this image is returned i am setting this image to scrollview and displaying it and deleting the previous image from scrollview.
More Info:-
Just to verify i commented out setting image to scrollview and just downloaded images on each swipe but still it crashes around 30 images
Surprisingly I am using same class DownloadImage.h and .m to download image at other places in the same work and it work awesome even with 500images .
I am testing in iPod Touch and I checked the memory utilised remain between 12-14mb(never exceed this)
Please help me out guys,let me know if you need more detail.
Its crashing because all the images are being stored in virtual memory, you need to be caching them and then loading them back into memory when the user actually views them.
Try also setting your image objects to nil after they have been cached or are not needed.
In your class I would also recommend using the didReceiveMemoryWarning method and release some of your images from memory when this is called.

iPhone - Make UITableViewCells appear on view load before NSURLConnection completes

I am trying to create a loose version of LazyTabelImages using storyboard and JSON. in ViewDidLoad on my main TableViewController, I start an NSURLConnection to get the JSON data, but my cells do not load until after the connection is completed. I want the same behavior that LazyTableImages has, where the cells load as blanks, but then have the information filled in (reload the table data). I can duplicate this if I do not use storyboard, as LazyTables does not use storyboard, but that is not an option.
I have looked through LazyTableImages to try to find the solution, but storyboard make a big difference (to me anyway).
Is there a simple way to get the cells to load as blanks? For example, if the device has no internet, I still want my TableView to show up, and I will put a custom message in the cell.
Code:
The part of my viewDidLoad where I initialize the connection....
NSURLRequest *urlrequest = [NSURLRequest requestWithURL:[NSURL URLWithString:serverURL]];
self.dataConnection = [[NSURLConnection alloc] initWithRequest:urlrequest delegate:self];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
connectionDidFinnishLoading...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//ListData below is an array that my data received (JSON) is loaded into. It is then passed to getTableData.
self.dataConnection = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelectorOnMainThread:#selector(getTableData:) withObject:ListData waitUntilDone:YES];
});
}
getTableData...
-(void)getTableData:(NSData *)jsonData
{
NSError *error = nil;
arrayEntries = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:&error];
for (int x = 0; x < arrayEntries.count; x++)
{
NSMutableDictionary *dic = [arrayEntries objectAtIndex:x];
//ARecord is a class just like in LazyTableImages that creates objects to keep the icons/data together. The ARecords are loaded into the TableView
ARecord *arecord = [[ARecord alloc] init];
NSString *title = [dic objectForKey:#"title"];
NSString *subt = [dic objectForKey:#"subtitle"];
NSString *url = [dic objectForKey:#"image_URL"];
arecord.Icon = nil;
arecord.URL = url;
arecord.Name = title;
arecord.title = subt;
//this is where I load an array full of the arecord objects.
[array addObject:arecord];
}
[self.tableView reloadData];
}
I've done something similar. In viewDidLoad: I set the array for table data to a few objects of [NSNull null] for however many blank rows I want to show while the data is downloading. In cellForRowAtIndexPath: I check if [self.arrayOfTableData objectAtIndex:indexPath.row] = [NSNull null]. If so return a "blank" cell, otherwise load the cell with ARRecrod data.
Then when the URL completes, replace the array of NSNulls with array of your ARRecords.
I do this with two objects. First, I have an image fetcher class that downloads data asynchronously and notifies a delegate when it's complete. Then I have an image view class that implements the fetcher's delegate methods. So something like:
#implementation AsyncImageFetcher
-(id)initWithURL:(NSURL *)aURL andDelegate:(id<SomeProtocol>)aDelegate{
//...
NSURLRequest *req = [NSURLRequest requestWithURL:aURL];
//Note that NSURLConnection retains its delegate until the connection
//terminates! See comments in MyImageView below.
[NSURLConnection connectionWithRequest:req delegate:self];
//...
}
//Implement standard connection delegates here. The important part is:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
// ...
UIImage *anImage = [self decodeDownloadedDataIntoAnImage];
if([[self delegate] respondsToSelector:#selector(imageFetcher:didFetchImage:)]){
[[self delegate] imageFetcher:self didFetchImage:anImage];
}
//...
}
#end
Then I subclass UIImageView or UIView or something (depending on how flexible you need to be) to implement the delegate protocol and fire off the fetcher:
#implementation MyImageView
-(id)initWithURL:(NSURL *)aURL andPlaceHolderImage:(UIImage *)aPlaceHolder{
//...
[self setImage:aPlaceHolder];
//Note we don't assign this to an ivar or retain it or anything.
//That's ok because the NSURLConnection inside the fetcher actually
//retains the fetcher itself. So it will live until the connection
//terminates -- which is exactly what we want. Thus we disable
//CLANG's noisy warnings.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value"
[[AsyncImageFetcher alloc] initWithURL:aURL andDelegate:self];
#pragma clang diagnostic pop
return self;
}
-(void)imageFetcher:(MCMAsyncImageFetcher *)anImageFetcher didFetchImage:(UIImage *)anImage{
[self setImage:anImage];
}
#end
In your specific case, you'd just set a MyImageView as your cell's imageView in -tableView:cellForRowAtIndexPath:, passing reasonable values for its placeholder and URL, of course.
Since I haven't see your code, I just give my suggestion here:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create(NULL, NULL);
dispatch_async(queue, ^{
//add your connection code here
//parse the json and store the data
//
dispatch_async(dispatch_get_main_queue(), ^{
//here to reload your table view again,
//since UI related method should run on main thread.
[YOUR_TABLEVIEW reloadData];
});
});
[YOUR_TABLEVIEW reloadData];
}
Note: Make sure your tableview in storyboard has connected to that in code! Hope it helps!

Objective-C: Asynchronously populate UITableView - how to do this?

I can't seem to find any info on this question, so I thought I'd ask the community.
Basically, I have a UITableView and I want to show an activity indicator while its data is loading from my server.
Here is some example code of what I'm trying to do (I'm using ASIHttpRequest).
//self.listData = [[NSArray alloc] initWithObjects:#"Red", #"Green", #"Blue", #"Indigo", #"Violet", nil]; //this works
NSString *urlStr=[[NSString alloc] initWithFormat:#"http://www.google.com"]; //some slow request
NSURL *url=[NSURL URLWithString:urlStr];
__block ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setCompletionBlock:^{
self.listData = [[NSArray alloc] initWithObjects:#"Red", #"Green", #"Blue", #"Indigo", #"Violet", nil]; //this doesn't work...
[table reloadData];
}];
[request setFailedBlock:^{
}];
[request startAsynchronous];
The dummy request to google.com does nothing - it just creates a delay and in the response I hope to repopulate the table with some JSON response from my own website.
But when I try to populate the table with the colours, nothing happens! I just get a blank table... If I uncomment the line above, it works fine, it's just on http responses things don't work for me.
Any suggestions greatly appreciated.
Edit:
I did a [self.tableView reloadData]; and now it works...
Stop using ASIHTTPRequest. NSURLConnection is not hard to use and will result in better, more performant code.
Your JSON response should be fed into a data structure not the UI. I recommend Core Data.
The data structure should feed your UITableView. Again, I recommend Core Data.
I would suggest reviewing how MVC works, you are short circuiting the design and that is the core problem.
SPOILER
Here is a more detailed how to. First you want the data retrieval to be async. Easiest and most reusable way to do that is build a simple NSOperation subclass.
#class CIMGFSimpleDownloadOperation;
#protocol CIMGFSimpleDownloadDelegate <NSObject>
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;
#end
#interface CIMGFSimpleDownloadOperation : NSOperation
#property (nonatomic, assign) NSInteger statusCode;
- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate;
#end
This subclass is the most basic way to download something from a URL. Construct it with a NSURLRequest and a delegate. It will call back on a success or failure. The implementation is only slightly longer.
#import "CIMGFSimpleDownloadOperation.h"
#interface CIMGFSimpleDownloadOperation()
#property (nonatomic, retain) NSURLRequest *request;
#property (nonatomic, retain) NSMutableData *data;
#property (nonatomic, assign) id<CIMGFSimpleDownloadDelegate> delegate;
#end
#implementation CIMGFSimpleDownloadOperation
- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate
{
if (!(self = [super init])) return nil;
[self setDelegate:delegate];
[self setRequest:request];
return self;
}
- (void)dealloc
{
[self setDelegate:nil];
[self setRequest:nil];
[self setData:nil];
[super dealloc];
}
- (void)main
{
[NSURLConnection connectionWithRequest:[self request] delegate:self];
CFRunLoopRun();
}
- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)resp
{
[self setStatusCode:[resp statusCode]];
[self setData:[NSMutableData data]];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)newData
{
[[self data] appendData:newData];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
[[self delegate] operation:self didCompleteWithData:[self data]];
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
[[self delegate] operation:self didFailWithError:error];
CFRunLoopStop(CFRunLoopGetCurrent());
}
#synthesize delegate;
#synthesize request;
#synthesize data;
#synthesize statusCode;
#end
Now this class is VERY reusable. There are other delegate methods for NSURLConnection that you can add depending on your needs. NSURLConnection can handle redirects, authentication, etc. I strongly suggest you look into its documentation.
From here you can either spin off the CIMGFSimpleDownloadOperation from your UITableViewController or from another part of your application. For this demonstration we will do it in the UITableViewController. Depending on your application needs you can kick off the data download wherever makes sense. For this example we will kick it off when the view appears.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSURLRequest *request = ...;
CIMGFSimpleDownloadOperation *op = [[CIMGFSimpleDownloadOperation alloc] initWithURLRequest:request andDelegate:self];
[[NSOperationQueue mainQueue] addOperation:op];
[self setDownloadOperation:op]; //Hold onto a reference in case we want to cancel it
[op release], op = nil;
}
Now when the view appears an async call will go and download the content of the URL. In this code that will either pass or fail. The failure first:
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;
{
[self setDownloadOperation:nil];
NSLog(#"Failure to download: %#\n%#", [error localizedDescription], [error userInfo]);
}
On success we need to parse the data that came back.
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
{
[self setDownloadOperation:nil];
NSLog(#"Download complete");
//1. Massage the data into whatever we want, Core Data, an array, whatever
//2. Update the UITableViewDataSource with the new data
//Note: We MIGHT be on a background thread here.
if ([NSThread isMainThread]) {
[[self tableView] reloadData];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[[self tableView] reloadData];
});
}
}
And done. A few more lines of code for you to write but it replaces 13K+ lines of code that gets imported with ASI resulting in a smaller, leaner, faster application. And more importantly it is an app that you understand every single line of code.
This is the problem
request setCompletionBlock:^{
self.listData = [[NSArray alloc] initWithObjects:#"Red", #"Green", #"Blue", #"Indigo", #"Violet", nil]; //this doesn't work...
[table performSelectorOnMainThread:#selector(reloadTable) withObject:nil waitUntilDone:NO];
}];
The reload table needs to be done on the main thread.
I tried NWCoder's solution, and it didn't work because its calling the wrong method. This is what i used.
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];

images not being added to NSMutableDictionary, which is within an NSOperation

So I first coded all my methods in a viewcontroller with an NSOperationQueue. After doing some research and a lot of reading I realized i had to subclass my loadImage operation so that I may use isCancelled and cancelAllOperations. So I went ahead and created an nsoperation class and called it from my viewcontroller. ALl the methods are called, even the imageLoaded, but the NSMutableDictionary remains empty. I use the dictionary to populate my tableviewcells using the url as the Key. Also be aware that the operation call in the viewcontroller is within a method which is called by an NSInvocationOperation when the view loads.
#interface loadImages : NSOperation {
NSURL *targetURL;
}
#property(retain) NSURL *targetURL;
- (id)initWithURL:(NSURL*)url;
#end
implementation of nsoperation class which includes some other calls to resize the image
#implementation loadImages
#synthesize targetURL;
- (id)initWithURL:(NSURL*)url
{
if (![super init]) return nil;
[self setTargetURL:url];
return self;
}
- (void)dealloc {
[targetURL release], targetURL = nil;
[super dealloc];
}
- (void)main {
NSLog(#"loadImages.m reached");
StoriesTableViewController *stories = [[StoriesTableViewController alloc] init];
NSMutableDictionary *tempDict = stories.filteredImagesDict;
UIImage *myImage = [[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[self targetURL]]]autorelease];
UIImage *scaledImage = [[[UIImage alloc] init] autorelease];
CGRect frame = CGRectMake(100.0f, 100.0f, 180.0f, 180.0f);
UIImageView *myImageFrame = [[UIImageView alloc] initWithFrame:frame];
myImage = [[myImage croppedImage:[myImageFrame bounds]]retain];
scaledImage = [[myImage resizedImage:CGSizeMake(120.0f, 120.0f) interpolationQuality:kCGInterpolationHigh]retain];
[tempDict setValue:scaledImage forKey:[self targetURL]];
[stories performSelectorOnMainThread:#selector(imageLoaded:)
withObject:myImage
waitUntilDone:YES];
NSLog(#"targetURL %#",[self targetURL]);
NSLog(#"tempDict count: %d",tempDict.count);
[stories release];
[myImage release];
[myImageFrame release];
[scaledImage release];
}
creation of nsoperation on viewcontroller
for(int i=0;i<storyQuantity;i++) {
NSString *imageString = [[[storiesArray objectAtIndex:i] objectForKey: #"image"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // must add trimming to remove characters
NSURL *url = [NSURL URLWithString:imageString];
loadImages *imageOperation = [[loadImages alloc] initWithURL:url];
[queue_ addOperation:imageOperation];
[imageOperation release];
}
If this is running without exceptions and the dictionary is still empty, it most likely means that your value is nil.
This is a common problem with code like that where you have the result of one method going into the next. At any point if there is a problem, all the rest of the chain will not work.
To solve, I would start right above where you assigned the image to the dictionary. You can use either a breakpoint, or an NSLog to determine the value of the image at that point. I prefer to use an NSLog, but a break point would let you look at all the values at once. If scaledImage is nil, then check myImage. Keep going back up the chain until you find the point where the value goes from what you would expect, to nil.

Possible risk with asynchronous request and delegation

I would like to add to UIImageView the capacity to set an image with an url. As result I would like to do something like.
[anImageView setImageWithContentAtUrl:[NSURL URLWithString:#"http://server.com/resource.png"]];
So I created a category (code below).
NSString *kUserInfoImageViewKey = #"imageView";
NSString *kUserInfoActivityIndicatorKey = #"activityIndicator";
#implementation UIImageView (asynchronous)
#pragma mark -
- (void)setImageWithContentAtUrl:(NSURL *)imageUrl andActivityIndicator:(UIActivityIndicatorView *)activityIndicatorOrNil {
[activityIndicatorOrNil startAnimating];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:self forKey:kUserInfoImageViewKey];
[dict setValue:activityIndicatorOrNil forKey:kUserInfoActivityIndicatorKey];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:imageUrl];
request.delegate = self;
request.userInfo = dict;
[dict release];
[request startAsynchronous];
}
#pragma mark -
#pragma mark private
- (void)requestFinished:(ASIHTTPRequest *)aRequest {
// get concerned view from user info
NSDictionary *dictionary = aRequest.userInfo;
UIImageView *imageView = (UIImageView *)[dictionary valueForKey:kUserInfoImageViewKey];
UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *) [dictionary valueForKey:kUserInfoActivityIndicatorKey];
[activityIndicator stopAnimating];
NSData *responseData = [aRequest responseData];
UIImage * image = [[UIImage alloc] initWithData:responseData];
imageView.image = image;
[image release];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
}
An ASIHTTPRequest is created and launched with the image as delegate. I think there is a risk if image is deallocated before ASIHTTPRequest return a result.
So, maybe adding a retain in setImageWithContentAtUrl: and adding a release in requestFinished: and requestFailed: but I'm not very confident.
How is it possible to do such things ?
Regards,
Quentin
Quentin,
I regularly use ASIHTTPRequest for asynchronous calls, so I know where you're coming from here. Also, it's a pain to set up for the first time, but did you know that Three20 library's TTImageView (I think that's it) already does what you are trying to do? It will even cache the image locally so you don't have to load it every time. Anyway.
Your worry is correct: ASIHTTPRequest is a wrapper on an NSOperation object (it's actually a subclass), so the NSOperationQueue will retain ASIHTTPRequest as long as the request is active.
If your user changes the view (say, on a nav bar controller), which then deallocs your UIImageView, your code may crash when it tries to call back to the delegate. So, when you dealloc your image view, it's better to hold on to a reference to the request and then cancel it.
Rather than a category, this may be one of those times where subclassing is better - because you'd want to overwrite the dealloc method (this is how I've handled this issue).
First, add this property to your subclass:
#property (nonatomic, retain) ASIHTTPRequest *request;
Then add this line to your method so you can hold on to it:
self.request = request;
And finally, in your ASIHTTPRequest delegate methods, destroy the reference:
self.request = nil;
Then your dealloc could look like this:
- (void) dealloc
{
if (self.request)
{
// Cancels the NSOperation so ASIHTTPRequest doesn't call back to this
[self.request cancel];
}
[request release];
[super dealloc]
}