Advertise Books Events Forum News Social Networking Support Us

sdkIQ for iPhone
($4.99)

Shape Up
($0.99)

Your First iPhone App
($1.99)

iVidCam Free
(free)

Kid Art
($0.99)

iPUBQUIZ
(£1.19)

ArtStudio
($3.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Development

Reply
 
LinkBack Thread Tools Display Modes
Old 06-12-2009, 02:58 PM   #1 (permalink)
Registered Member
 
Join Date: Mar 2009
Location: Toronto, ON
Posts: 111
Default Asynchronous image download/caching performance issue

I will try to explain what I am trying to do and what my issue is to my best ability.

I have a UITableView, with 5-6 cells on the screen at any given time. In each cell, I have a different image (depending on the content of the cell). The image file name is obtained from an XML feed (like the rest of the cell content).

TableViewController code:

Code:
// get the string and parse it into a url to create the final image URL
NSString *param = anObject.imageFileName;
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", param];
NSString *urlString = [NSString stringWithFormat: @"http://www.[redacted].com/images%@", fileName];
NSURL *imageURL = [NSURL URLWithString:urlString];

// draw a default image until an image is downloaded from URL 
UIImage *blankImage = [[UIImage imageNamed:@"blankImage.png"] retain];
cell.cellImage = blankImage;
[blankImage release];

// draw the image from URL connection or from cache (see code below)
cell.cellImageURL = imageURL;
As you can see above, I get/set the filename (so it knows what to download and what to save/retrieve the image as locally). For pretty UX I just draw a default image before the URL connection has been able to retrieve the image (if an image is found locally the change from default to local image will happen before you see it anyway).

Below is my code in my TableViewCell

Code:
- (void)setCellImageURL:(NSURL*)url
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *homeDirectoryPath = [paths objectAtIndex:0];
NSString *unexpandedPath = [homeDirectoryPath stringByAppendingString:@"/Images/"];
// folderPath is setup in the header
folderPath = [[NSString pathWithComponents:[NSArray arrayWithObjects:[NSString stringWithString:[unexpandedPath stringByExpandingTildeInPath]], nil]] retain];

if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath isDirectory:NULL]) {
	[[NSFileManager defaultManager] createDirectoryAtPath:folderPath attributes:nil];
}

fileName = [[url path] lastPathComponent];
fileName = [[folderPath stringByAppendingPathComponent:fileName] retain];

// check for the image in the cache.	
if ([[NSFileManager defaultManager] fileExistsAtPath:fileName]) {
	UIImage *localImage = [[[UIImage alloc] initWithContentsOfFile:fileName] retain];
	self.cellImage = localImage;
	[localImage release];
	return;
}
	
if (connection != nil) { [connection release]; }
if (data != nil) { [data release]; }

NSURLRequest* request = [NSURLRequest requestWithURL:url
								       cachePolicy:NSURLRequestUseProtocolCachePolicy
								 timeoutInterval:60.0];
    connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {	
    if (data == nil) { data = [[NSMutableData alloc] initWithCapacity:2048]; }
    [data appendData:incrementalData];	
}

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {	
	[connection release];
	connection = nil;
	self.cellImage = [[UIImage imageWithData:data] retain];
	[self setNeedsDisplay];
	// save image if it's not already cached
	if (cellImage != nil) {
		CGFloat compressionQuality = 1.0;
		NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(cellImage, compressionQuality)];
		[imageData writeToFile:fileName atomically:YES];
		
	} else {
	// if the data == nil, basically, there's no image on the server, then "re"display the default image
		self.cellImage = [[UIImage imageNamed:@"blankImage.png"] retain];
	}

	[cellImage release];
	[data release];
	data = nil;
}
I've used code from this blog: http://www.markj.net/iphone-asynchronous-table-image/ which helped me setup the asynchronous image downloading, and then I found another source for simply setting up the local folder /Images/ in the app's sandbox (which is done every time the image is called in my tableviewcontroller) and then saved in connectionDidFinishLoading:

Everything works great. EXCEPT, it's slow... I am drawing the cell, so scrolling worked flawlessly until I set up the part where I do the folderPath and save/retrieve the image on disk. I believe that's the part that slow down my table view.

The problem is, I have tried to setup the Directory and folderPath in my AppDelegate, so it just happens on app launch, but then it seems my TableViewCell is unable to get the folderPath correctly. It's just nil! And yes, I am importing the AppDelegate class, etc. correctly, because I am importing the AppDelegate class in my TableViewController and there's it's not nil.

I can provide additional code if needed. Hoping someone out there can assist. Thanks!
CanadaDev is offline   Reply With Quote
Old 06-12-2009, 05:03 PM   #2 (permalink)
Senior Member
iPhone Dev SDK Supporter
 
smasher's Avatar
 
Join Date: Jul 2008
Location: San Mateo, CA (San Fran)
Posts: 2,575
Default

Quote:
Originally Posted by CanadaDev View Post
The problem is, I have tried to setup the Directory and folderPath in my AppDelegate, so it just happens on app launch, but then it seems my TableViewCell is unable to get the folderPath correctly. It's just nil! And yes, I am importing the AppDelegate class, etc. correctly, because I am importing the AppDelegate class in my TableViewController and there's it's not nil.
You're saying that you create folderPath every time you need an image because you tried creating it just once, at the start of the app, but when you tried to use it later it was nil?

That's probably becuase [NSString pathWithComponents] returns an autoreleased string - that string will get released unless you retain it in some way, like putting it in a property. If you've created a property, then this should do the trick:
Code:
self.folderPath = BLAH BLAH
otherwise, you just need a [folderPath retain].

PS - the way you're building the folderpath looks strange - you turn a string into an array with one item into a path. I think this would work just as well:
Code:
self.folderPath =[homeDirectoryPath stringByAppendingPathComponent:@"Images"];
PPS - if things are still slow, consider keeping an array of filenames that you've already loaded - then you could check if an image is cached by checking if it's in the array, instead of hitting the filesystem with fileExistsAtPath.

Also run through your code with the debugger - make sure that, for some reason, it isn't recreating the Images directory every time, or failing to use the cache for some reason.
__________________
smasher is offline   Reply With Quote
Old 06-15-2009, 11:51 AM   #3 (permalink)
Registered Member
 
Join Date: Mar 2009
Location: Toronto, ON
Posts: 111
Default

Quote:
Originally Posted by smasher View Post
You're saying that you create folderPath every time you need an image because you tried creating it just once, at the start of the app, but when you tried to use it later it was nil?
No it's not nil. I think the problem is that it's doing too much work when it loops through the code for every cell, or every time it loads the cell (on a scroll).

Quote:
That's probably becuase [NSString pathWithComponents] returns an autoreleased string - that string will get released unless you retain it in some way, like putting it in a property. If you've created a property, then this should do the trick:
Code:
self.folderPath = BLAH BLAH
otherwise, you just need a [folderPath retain].

PS - the way you're building the folderpath looks strange - you turn a string into an array with one item into a path. I think this would work just as well:
Code:
self.folderPath =[homeDirectoryPath stringByAppendingPathComponent:@"Images"];
Tried that. Removed the last line of code for the folderPath, and took your advice above. Still works as it should, although still quite slow on device.

Quote:
PPS - if things are still slow, consider keeping an array of filenames that you've already loaded - then you could check if an image is cached by checking if it's in the array, instead of hitting the filesystem with fileExistsAtPath.
I'll try to set that up today then. Will have to look into how.

Quote:
Also run through your code with the debugger - make sure that, for some reason, it isn't recreating the Images directory every time, or failing to use the cache for some reason.
I've now changed some stuff around so it only sets up the folder in the AppDelegate, but as far as I can see it only runs once.
CanadaDev is offline   Reply With Quote
Old 06-15-2009, 01:20 PM   #4 (permalink)
Registered Member
 
bharath2020's Avatar
 
Join Date: Oct 2008
Location: Bengaluru
Posts: 123
Default

Where exactly do you invoke setCelllImageURL:?

If its in the tableview delegate method objectforcellatrow.. then i suppose it is the wrong place..


A simple solution is

Create a own ImageCache class (it will be handy in your future projects, as it has helped me a lot)

provide a request method to other classes

Use an queue internally to maintain and process the request,
the request should contain the url, the sender object(object responsible to received the image after loading) and some context info to return back to the caller...


process each request in a separate thread

once the request is processed(i.e. image is loaded) then inform the sender object along with context info

now store the image in the cache (either a mutable dictionary or array) through which you can fetch the image if requested in future..


you can check for the size of total images and keep a constraint on the total size of the cache



(Sender object in your case would be your UITableViewController or the appropriate class)


Hope that explains well..
bharath2020 is offline   Reply With Quote
Old 06-15-2009, 03:05 PM   #5 (permalink)
Registered Member
 
Join Date: Mar 2009
Location: Toronto, ON
Posts: 111
Default

Quote:
Originally Posted by bharath2020 View Post
Where exactly do you invoke setCelllImageURL:?

If its in the tableview delegate method objectforcellatrow.. then i suppose it is the wrong place..
It's invoked in cellForRowAtIndexPath: in my tableviewcontroller class.

Quote:
A simple solution is

Create a own ImageCache class (it will be handy in your future projects, as it has helped me a lot)

provide a request method to other classes

Use an queue internally to maintain and process the request,
the request should contain the url, the sender object(object responsible to received the image after loading) and some context info to return back to the caller...


process each request in a separate thread

once the request is processed(i.e. image is loaded) then inform the sender object along with context info

now store the image in the cache (either a mutable dictionary or array) through which you can fetch the image if requested in future..


you can check for the size of total images and keep a constraint on the total size of the cache



(Sender object in your case would be your UITableViewController or the appropriate class)


Hope that explains well..
Okay, I'll look into this. Find it odd there's no code available that already has all this stuff setup. I mean it's a pretty common thing, and a lot of apps use it. Haven't found a complete code only small pieces here and there.
CanadaDev is offline   Reply With Quote
Old 06-29-2009, 10:18 AM   #6 (permalink)
Registered Member
 
Join Date: May 2009
Posts: 9
Default

Hi !

I'm also trying to do somehing like that (like the rss readers do) and it's hard to have a fast scroll, with caching images etc. I also found a tip : have a custom cell view et put a view into it instead of managing directly in your custom cell, right here : Fast Scrolling in Tweetie with UITableView

(And Apple update the code for custom cell)

Have you any tips to give for caching, or improving performance ? thanks
ipodishima is offline   Reply With Quote
Old 06-30-2009, 07:35 AM   #7 (permalink)
Registered Member
 
Join Date: Mar 2009
Location: Toronto, ON
Posts: 111
Default

Quote:
Originally Posted by ipodishima View Post
Hi !

I'm also trying to do somehing like that (like the rss readers do) and it's hard to have a fast scroll, with caching images etc. I also found a tip : have a custom cell view et put a view into it instead of managing directly in your custom cell, right here : Fast Scrolling in Tweetie with UITableView

(And Apple update the code for custom cell)

Have you any tips to give for caching, or improving performance ? thanks
Not sure if you read my post, but that's exactly what I am doing, however I still had scrolling issues. Those are now almost fully resolved, and I simple don't think it's possible for me to do more seeing I use images that are a bit larger that the ones in other apps, plus I have to resize the images on the fly. I optimized my scrolling even further, by compressing the images upon saving them, which helped the load time a bit.

Also if you have ANY NSLogs get rid of them. They slow everything down a lot.
CanadaDev is offline   Reply With Quote
Old 07-01-2009, 05:28 AM   #8 (permalink)
Registered Member
 
Join Date: May 2009
Posts: 9
Default

Yes i read your post. ^^


I didn't try to resize images, but i improved the scrolling performance a little bit adding images in a dictionary.

I posted 'cause i thought you had a better solution.

And yes, it is better when i remove all NSLogs.

Thanks by the way !
ipodishima is offline   Reply With Quote
Old 07-01-2009, 08:02 AM   #9 (permalink)
Registered Member
 
Join Date: Mar 2009
Location: Toronto, ON
Posts: 111
Default

Quote:
Originally Posted by ipodishima View Post
Yes i read your post. ^^


I didn't try to resize images, but i improved the scrolling performance a little bit adding images in a dictionary.

I posted 'cause i thought you had a better solution.

And yes, it is better when i remove all NSLogs.

Thanks by the way !
Well the images I download are a bit bigger than the size I need for the tableviewcell, so I have to resize on the fly (happens automatically, but still requires a bit of processing power, unfortunately)

I have no done a dictionary, but have been thinking about using one. I am going through NSFileManager to check for a local file, which does require a bit more CPU than just checking a dictionary on the fly.

How did you set up your NSDictionary?

Thanks
CanadaDev is offline   Reply With Quote
Old 07-01-2009, 12:24 PM   #10 (permalink)
Registered Member
 
Join Date: May 2009
Posts: 9
Default

So, i do like this : in - (UITableViewCell *)tableViewUITableView *)tableView cellForRowAtIndexPathNSIndexPath *)indexPath .


Code:
// Asynchrone View yipi
	AsyncUIImageView* asyncImage = [[[AsyncUIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)] autorelease];
    asyncImage.tag = 999;

	if([imageDict objectForKey:[[xmlParser.stories objectAtIndex:storyIndex] objectForKey:@"title"]])
	{
		// load from dict, ie cache	
		asyncImage = [imageDict objectForKey:[[xmlParser.stories objectAtIndex:storyIndex] objectForKey:@"title"]];
	}
	else {
		// load photo from web
		[asyncImage loadImageFromRawURLMadeOfString:[[xmlParser.stories objectAtIndex:storyIndex] objectForKey:@"article-body"]];
		// add into the dict
		[imageDict setObject:asyncImage forKey:[[xmlParser.stories objectAtIndex:storyIndex] objectForKey:@"title"]];
	}
with a NSMutableDictionary, of course.

I have to improve how i put the photo into the dictionary, because if i save into a .plist, i don't want to save all the photos ^^

++
ipodishima is offline   Reply With Quote
Reply

Bookmarks

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On


Enter the iPhone App Challenge!  Win $500!
» Advertisements
» Online Users: 281
19 members and 262 guests
cakesy, camtadao, crooksy88, Erle, Eskema, Falcon80, ghanalupo, jojo453, josh, kiancheong, mixer555, odysseus31173, SalvoMaltese, shay_somech, SkodaBuddy, StefanL, Stitch, Susan03, thadre
Most users ever online was 779, 05-11-2009 at 09:55 AM.
» Stats
Members: 24,280
Threads: 39,072
Posts: 171,337
Top Poster: smasher (2,575)
Welcome to our newest member, Susan03
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 04:10 AM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2010, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.2.0