I have an Objective C application that reads data from an RSS feed using ASIHTTPRequest and parses it using GDataXML. There are two pieces of the RSS data returned that I'd like to display in a table. Each item returned in the RSS feed has a image URL and a description. I'd like to populate the table with the image in one cell and the description in the next cell to the right of the image. My current logic is looping over each item returned from the RSS feed and populating a UIImage object using the following logic:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.example.com/image.png"]]];
cell.image = [UIImage imageWithData: image];
This works, but seems to run very slowly. Sometimes the application just freezes while trying to load the pictures. Is there a more optimal way to load the images in the table? If I ignore the images, the table populates with the text data quickly.
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.example.com/image.png"]]];
In the above line of code, NSURL URLWithString: is to use with file system URL like already downloaded image in your temp directory or plist file etc. Since you have passed a http URL it makes synchronous request blocking your thread until it finishes downloading image data. And if you are doing this on main thread then UI doesn't respond to touches until image gets downloaded.
Change it to make asynchronous call, i.e., download example.com/image.png asynchronously. You may refer to ASIHTTPRequest instance method startAsynchronous and then pass downloaded data to UIImage imageWithData:
To learn more about asynchronous network requests, watch session 208 WWDC 2010 Network Apps for iPhone OS Part-2
Related
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[getImage objectAtIndex:indexPath.row]]];
cell.imageView.image = [UIImage imageWithData:imageData];
I am fetching images from a server and displaying them on a UITableView. I'm finding that the scrolling is very slow. How do I remove this?
When you call this code:
[[NSData alloc] initWithContentsOfURL:...]
this is reaching out over the network to get data. This can take several seconds (or longer), whereas you want to maintain 60 frames per second drawing. This means that your tableView:cellForRowAtIndexPath: method should execute in under 1/60 seconds, or 16 milliseconds, so network calls are out of the question.
The appropriate way to write this code is to load data asynchronously (using Grand Central Dispatch or another mechanism), cache it either on disk or using NSCache, and then informing the table view that it should reload to display newly-available data.
That datasource method will be called frequently, even for recently displayed cells, as iOS wants to reduce the total number of cells in the tableview, so it will destroy cells that are no longer visible and ask the tablview datasource method to recreate them when they become visible.
If you are fetching from the server for every cell population call then it's bound to be slow (an understatement).
Implement some caching and populate your content from the cache.
I have my own image downloader class, it holds a queue and downloads images one (or a certain amount) at a time, writes them to the cache folder and retrieves them from the cache folder when necessary. I also have a UIImageView subclass to which I can pass a URL, through the image downloader class it will look if the image already exists on the device and show it if it does, or download and show it after it finished.
After an image finishes downloading I do the following. I create a UIImage from the downloaded NSData, save the downloaded NSData to disk and return the UIImage.
// This is executed in a background thread
downloadedImage = [UIImage imageWithData:downloadedData];
BOOL saved = [fileManager createFileAtPath:filePath contents:downloadedData attributes:attributes];
// Send downloadedImage to the main thread and do something with it
To retrieve an existing image I do this.
// This is executed in a background thread
if ([fileManager fileExistsAtPath:filePath])
{
NSData* imageData = [fileManager contentsAtPath:filePath];
retrievedImage = [UIImage imageWithData:imageData];
// Send retrievedImage to the main thread and do something with it
}
As you can see, I always create a UIImage directly from the downloaded NSData, I never create NSData using UIImagePNGRepresentation so the image never gets compressed. When you create a UIImage from compressed NSData, UIImage will decompress it right before rendering on the main thread and thus block the UI. Since I'm now having a UITableView with a ton of small images in it that have to be downloaded or retrieved from disk, this would be unacceptable as it would slow down my scrolling immensely.
Now my problem. The user is also able to select a photo from the camera roll, save it and it also has to appear in my UITableView. But I can't seem to find a way to turn the UIImage from the camera roll into NSData without using UIImagePNGRepresentation. So here's my question.
How can I convert a UIImage into uncompressed NSData so I can convert it back to a UIImage later using imageWithData so that it doesn't have to be decompressed before rendering?
or
Is there any way I can do the decompression before sending the UIImage to the main thread and cache it so it only has to be decompressed once?
Thanks in advance.
How can I convert a UIImage into uncompressed NSData so I can convert it back to a UIImage later using imageWithData so that it doesn't have to be decompressed before rendering?
What you're really asking here, I take it, is how to store the UIImage on disk in such a way that you can later read the UIImage from disk as fast as possible. You don't really care whether it is stored as NSData; you just want to be able to read it quickly. I suggest you use the ImageIO framework. Save by way of an image destination and fetch later by way of an image source.
http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Conceptual/ImageIOGuide/ikpg_dest/ikpg_dest.html
Is there any way I can do the decompression before sending the UIImage to the main thread and cache it so it only has to be decompressed once?
Yes, good question. That was going to be my second suggestion: use threading. This is what people have to do with tables all the time. When the table asks for the image, you either have the image already or you don't. If you don't, you supply a filler image and, in the background, fetch the real image. When the real image is ready, you have arranged to get a notification. Back on the main thread, you tell the table view to ask for the data for that row again; this time you've got the image and you supply it. The user will thus see a slight delay before the image appears. I'm sure you've seen lots of apps that behave this way (New York Times is a good example).
I have one further suggestion, and it may be the best of all. You speak of it taking time to decompress the image from disk. But this should take no time at all if the image is small. But the image should be small, because it's going to go into a small place - a table cell. In other words, you should shrink the images beforehand, when you first receive them, so that you are ready with the small version of each image when asked. It is a huge waste of time and memory to supply a large image that is to go into a small space.
ADDED LATER: Of course you do understand that a lot of this worry would be unnecessary if you weren't saving the images to disk. I'm not at all clear on why you need to do that. I hope you have a good reason for it; but it's a heck of a lot faster, obviously, if you just hold the images ready in memory.
I found solution:
CGImageRef downloadedImageRef = downloadedImage.CGImage;
CGDataProviderRef provider = CGImageGetDataProvider(downloadedImageRef);
NSData *data = CFBridgingRelease(CGDataProviderCopyData(provider));
// Then you can save the data
IF you download the data and save it to disk, then the data is compressed in either PNG, JPEG, or GIF format. You are not going to be downloading uncompressed image data. So, the root of your question about doing the decompression first needs to be addressed before you save the file to disk. Decompressing before you save will make the file a lot bigger, but it means that decompression is not needed before the data is read back into a CGImageRef or UIImage. It is the loading and then decompressing a bunch of images that is slowing down your CPU and making scrolling slow. But, it is not a solution to simply hold everything in memory already decompressed, because that will use up all your app memory and crash your phone before long. You might be able to get away with it for some small number of images, but this is a basic design flaw that you need to address when first writing your code. If you like, you can have a look at my blog post on this topic video-and-memory-usage-on-ios-devices, the post deals with video, but you have the exact same issue when dealing with lots of different images. I would suggest that you write your small images to disk in an uncompressed format like TIFF or BMP, that way reading them back in is easy as long as ImageIO supports that specific format.
Instruments is telling me that alot of memory is being allocated when I rapidly set the image name of a UIImageview in my app. I have a UIImageView that changes its image name every frame in my game. When profiled with zombie checking in instruments, the app seems to be constantly gaining live bytes at an enourmous rate. Is there a way that I can deallocate the UIImageView's current image to stop it from doing this? I am using ARC.
My code to assign the UIImageView's image is as follows:
aPlanet.image = [UIImage imageNamed:tempPlanetName];
Where aPlanet is the UIImageView and tempPlanetName is the name of the image. This is called every frame.
[UIImage ImageNamed:] method loads the image into image view and adds this newly created uiimage object to autorelease pool. To get rid of this problem you should use -
NSString *imgPath = [NSBundle mainbundle] pathForResource:#"imageName" ofType:#"png"];
aPlanet.image = [[UIImage alloc] ]initWithContentsOfFile:imgPath];
if you are using arc then you don't need to bother about releasing this newly allocated object of uiimage which was created using initWithContentsOfFile: method.
When you use UIImage imageNamed: it will load and cache that image file. This is intended for reuse of icons and other image resources that will be utilized more than once in your application.
Apart from it seeming somewhat unusual to update an image view with a new image every frame, you should look into alternative means of loading images that you will not need more than once - or even if you do, when you need more control over its lifecycle.
For example have a look at UIImage imageWithContentsOfFile: (documented here: Apple Developer Library Reference). It explicitly states that this method will not do any caching of the image contents.
I hope this helps you, but for every frame I doubt that your performance will be good enough with this approach, but this is probably the topic of a different question should the need arise.
I want to load multiple images when my app starts from a website (i.e. all images in http://hello.com/images/ which are named 1.png, 2.png, 3.png..) , so that the images can be used anywhere in the program without needing to reload them every time I want to access them.
Can I simply create a class that holds a static NSArray and fill it at the beginning, to then create an instance of this class whenever I need the images or is there a better way to do it?
Right now, I am loading the images with the following code:
UIImage *image =[[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://hello.com/images/%#.png,item]]]];
I want to make the app as efficient as possible, so I am concerned about the creation of multiple objects making it very demanding.
Thanks
You can try downloading the images asynchronously in a separate thread when the application starts
and use it later.
Here is the SO question and answer where the poster uses a custom class to download the images in the background asynchronously.
Try this for efficient download of images and UI also will not be blocked.
I am trying to write an iPad app that loads an image from a URL. I am using the following image loading code:
url = [NSURL URLWithString:theURLString];
NSData *data = [NSData dataWithContentsOfURL:url];
img = [[UIImage alloc] initWithData:data];
[imageView setImage:img];
[img release];
NSLog(#"Image reloaded");
All of that code gets added to a NSOperationQueue as an operation so it will load asynchronously and not cause my app to lock up if the image's websever is slow. I added the NSLog line so I could see in the console when this code finished executing.
I have noticed consistently that the image is updated in my app about 5 seconds AFTER the code finishes executing. However if I use this code on it's own without putting it in the NSOperationQUeue it seems to update the image almost immediately.
The lag is not caused entirely by a slow web server... I can load the image URL in Safari and it takes less than a second to load, or I can load it with the same code without the NSOperationQueue and it loads much more quickly.
Is there any way to reduce the lag before my image is displayed but keep using a NSOperationQueue?
According to the documentation, the code you have written is invalid. UIKit objects may not be called anywhere but on the main thread. I'll bet that what you're doing happens to work in most respects but doesn't successfully alter the display, with the screen being updated by coincidence for some other reason.
Apple strongly recommend that threads are not the way to perform asynchronous URL fetches if you want to remain battery efficient. Instead you should be using NSURLConnection and allowing the runloop to organise asynchronous behaviour. It's not that hard to write a quick method that just accumulates data to an NSData as it comes then posts the whole thing on to a delegate when the connection is complete but assuming you'd rather stick with what you've got I'd recommend:
url = [NSURL URLWithString:theURLString];
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:#selector(setImageViewImage:) withObject:data waitUntilDone:YES];
...
- (void)setImageViewImage:(NSData *)data
{
img = [[UIImage alloc] initWithData:data];
[imageView setImage:img];
[img release];
NSLog(#"Image reloaded");
}
performSelectorOnMainThread does what the name says — the object is sent to will schedule the selector requested with the object given as a single parameter on the main thread as soon as the run loop can get to it. In this case 'data' is an autoreleased object on the pool in the thread implicitly created by the NSOperation. Because you need it to remain valid until it has been used, I've used waitUntilDone:YES. An alternative would be to make data something that you explicitly own and have the main thread method release it.
The main disadvantage of this method is that if the image returns in a compressed form (such as a JPEG or a PNG), it'll be decompressed on the main thread. To avoid that without making empirical guesses about the behaviour of UIImage that go above and beyond what is documented to be safe, you'd need to drop to the C level and use CoreGraphics. But I'm taking it as given that doing so is beyond the scope of this question.
Tommy is correct about needing to do all UIKit stuff on the main thread. However, if you're running the fetch on a background operation queue, there's no need to use the NSURLConnection asynchronous loading. Also, by keeping the image decoding work on the background operation, you'll keep the main thread from blocking while decoding the image.
You should be able to use your original code as is, but just change [imgView setImage:img] to:
[imageView performSelectorOnMainThread:#selector(setImage:)
withObject:img
waitUntilDone:NO];