Pointer to Pointer to UIImage created by NSURLConnection - iphone

Hey all, here's the deal...
I've got a UIImage that is being loaded in the background via an NSURLConnection within a subclassed UIImageView. Until the data finishes downloading, a placeholder image is shown. The UIImage needs to be used by another, separate UIImageView, as well.
The problem I'm having is that if I set the second UIImageView's image property to that of the subclassed object before the download is complete, the second UIImageView never displays the downloaded image, since it's image prop is pointing to the placeholder.
Is there anyway to pass a pointer to a pointer between these two UIImageViews?
I've tried things like this:
imageView2.image = &[imageView1 image];
imageView2.image = *[imageView1 image];
But these don't seem to work. Any ideas?

Those pointers look particularly gross. The problem with doing it that way is that, even though you might be updating the data pointed to by those pointers, you are not notifying your UIImageView subclasses that the data has changed, and thus they don't know to redraw.
Instead of playing with that pointer mess, why not use Key-Value Observing instead? If you have a separate thread running that downloads the UIImage, you can just tell your UIImageViews to observe that property and when your downloader class is finished downloading all the data and has put it into the property, the views will get a notification and can then display the image.

Can't you just catch the connectionDidFinishLoading: message from the NSURLConnection and then set the second image?

Related

Asynchronously loading images on the iPhone

I have a grid of images that are loaded from the web, but there's a bit of lag/stutter while scrolling. I'm using an asynchronous ASIHTTPRequest to make the requests, so the download itself is happening in a separate thread, but because UIKit isn't thread-safe, once I receive the NSData response, I have to call UIImage initWithData on the main thread.
Profiling shows that, by far, the bottleneck consists of the internal PNG parsing functions invoked by UIImage initWithData. I'm interested in doing this in a background thread, so the main UI remains responsive and there's less lag.
But I'm not sure exactly how to do this. It sounds like the right direction is to use CGImageRef, since Core Graphics is thread-safe, but I only see CGImageCreateWithPNGDataProvider and CGImageCreateWithJPEGGDataProvider, whereas UIImage initWithData supports a large list of image types.
I want something that has the same functionality as UIImage initWithData but doesn't have the thread-safety issues.
You can call UIImage initWithData in a background thread safely. As a good rule of thumb, what you can't do in a background thread is alter user interface elements. In this case, you should not be setting the image property of a UIImageView, or adding a UIImageView to a superview in a background thread.
However, creating a UIImage instance is safe, and will work with no problems.

Passing Image data from UIImagePickerController off for background processing

I'm using a UIImagePickerController to capture a still image. I then need to do some processing work before saving different copies of the image to a Core Data store. The processing and saving work can take up to 4-8 seconds on an iPhone 4 so I'm trying to branch the work off to a background queue so the whole app and UI doesn't block.
At the root of my question is this. Is it possible to use a UIImage in a background thread so long as the UIImage object is totally confined to that thread? I found the following in apple's thread safety summary about NSImage. I'm assuming UIImage would work the same way.
NSImage Restrictions:
One thread can create an NSImage object, draw to the image buffer, and pass it off to the main thread for drawing. The underlying image cache is shared among all threads. For more information about images and how caching works, see Cocoa Drawing Guide.
Can anyone confirm this or is it just plain wrong to touch something like a UIImage object outside of the main thread. If using a confined UIImage instance is okay, then I that leads to another issue. The UIImagePickerController returns an NSDictionary which is thread safe, but within that NSDictionary is a UIImage object. Is it safe to pass that dictionary off to another thread then use the contained object to within that thread?
If the UIImage in the imagePicker info dictionary is not safe to use then any suggestions on how best to proceed?
I think I have the actual Core Data threading issues figured out. But for information I currently write and retrieve the image data using an NSValueTransformer to transform a UIImage to and from NSData within a custom NSManagedObject subclass.
I worked on some "Process an image after UIImagePickerController" code in a background thread just as you are and had no problems. Below are the steps I had success with.
Image Picker Delegate returns user image.
Assign image to the background thread class (In my case it was a #property retain'ed IVAR)
close/dismiss Image Picker
Background processing continues and creates a new UIImage after manipulating the original
new UIImage is returned to appropriate VC
Background process ends.

Shared UIImageView for background image throughout app - Singleton Property

I have a UIImage that I use throughout my app as a background image for grouped UITableViews.
I thought that for efficiency I would alloc and init the UIView with my UIImage in my appDelegate and then access throughout my app. That way I would only allocate that imageView once and if I was drilling into a nav stack with multiple tableviews with this image I wouldn't need to worry about releasing and restoring the image as I descend and ascend or incur overhead at each step.
As soon as I tried this I noticed that it seems that the UITableView class is releasing the my shared image down to 0 and it therefore is going away. Makes perfect sense but I would need to prevent the image from ever hitting a 0 retain count for this to work.
Is this a totally goofy approach?
If it is not what's the best way to retain my shared ImageView? I know I could call retain when I setup each tableview's backgroundimage but I was wondering if there is a way to set the retain count of the shared UIImageView to NSUIntegerMax in my appDelegate. I've setup singleton classes before but in this case I'm trying to have a single property that is never released rather than creating a UIImageView singleton subclass.
Sorry if that's a little muddled and thanks for any pointers.
I would not worry so much as + (UIImage *)imageNamed:(NSString *)name are cached.
From the spec:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.

Scrolling performance and UIImage drawing

I'm building a UITableView similar to iPod.app's album browsing view:
I'm importing all the artists and album artworks from the iPod library on first launch. Saving everything to CoreData and getting it back into an NSFetchedResultsController. I'm reusing cell identifiers and in my cellForRowAtIndexPath: method I have this code:
Artist *artist = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *identifier = #"bigCell";
SWArtistViewCell *cell = (SWArtistViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
cell = [[[SWArtistViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
cell.artistName = artist.artist_name;
cell.artworkImage = [UIImage imageWithData:artist.image];
[cell setNeedsDisplay];
return cell;
My SWArtistViewCell cell implements the drawRect: method to draw both the string and image:
[artworkImage drawInRect:CGRectMake(0,1,44,44)]
[artistName drawAtPoint:CGPointMake(54, 13) forWidth:200 withFont:[UIFont boldSystemFontOfSize:20] lineBreakMode:UILineBreakModeClip];
Scrolling is still choppy and I just can't figure out why. Apps like iPod and Twitter have butter smooth scrolling and yet they both draw some small image in the cell as I do.
All my views are opaque. What am I missing?
EDIT: here's what Shark says:
I'm not familiar with Shark. Any pointer as of what are these symbols related to? When I look at the trace of these, they all point to my drawRect: method, specifically the UIImage drawing.
Would it point to something else if the chokehold was the file reading? Is it definitely the drawing?
EDIT: retaining the image
I've done as pothibo suggested and added an artworkImage method to my Artist class that retains the image created with imageWithData:
- (UIImage*)artworkImage {
if(artworkImage == nil)
artworkImage = [[UIImage imageWithData:self.image] retain];
return artworkImage;
}
So now I can directly set the retained image to my TableViewCell as follow:
cell.artworkImage = artist.artworkImage;
I also set my setNeedsDisplay inside the setArtworkImage: method of my tableViewCell class. Scrolling is still laggy and Shark shows exactly the same results.
Your profiling data strongly suggests that the bottleneck is in the unpacking of your PNG images. My guess is that 58.5 % of your presented CPU time is spent unpacking PNG data (i.e. if the memcpy call is also included in the loading). Probably even more of the time is spent there, but hard to say without more data. My suggestions:
As stated before, keep loaded images in UIImage, not in NSData. This way you won't have to PNG-unpack every time you display an image.
Put the loading of your images in a worker thread, to not affect the responsiveness of the main thread (as much). Creating a worker is real easy:
[self performSelectorOnMainThread:#selector(preloadThreadEntry:) withObject:nil waitUntilDone:NO];
Preload images far ahead, like 100 rows or more (like 70 in the direction you're scrolling, keep 30 in the opposite direction). If all your images need to be 88x88 pixels on retina, 100 images would require no more than two MB.
When you profile more the calls to stuff named "png", "gz", "inflate" and so forth might not go way down your list, but they will certainly not affect the feeling of the application in such a bad way.
Only if you still have performance problems after this, I would recommend you look into scaling, and for instance loading "...#2x.png" images for retina. Good luck!
[UIImage imageWithData:] doesn't cache.
This means that CoreGraphic uncompress and process your image every single time you pass in that dataSource method.
I would change your artist's object to hold on a UIImage instead of NSData. You can always flush the image on memoryWarning if you get them a lot.
Also, I would not recommend using setNeedsDisplay inside the dataSource call, I would use that from within your cell.
SetNeedsDisplay is not a direct call to drawRect:
It only tells the os to draw the UIVIew again at the end of the runloop. You can call setNeedsDisplay 100 times in the same runloop and the OS will only call your drawRect method once.
If the delay's happening in your -drawRect, then you might want to take a look at this article: Tweetie's developer explains pretty thoroughly the method he used to get that smooth scrolling you're after. This has become a bit easier since then, though: CALayer has a shouldRasterize property that basically flattens its sublayers into a bitmap, which can then—as long as nothing changes inside the layer—give you much better performance when animating the layer around, as UITableView does when you scroll it. In this case, you'd probably apply that property to your individual UITableViewCells' layers.
My guess is that the delay is from storing images in Core Data. Core Data is usually not a good way to store large blobs of data.
A better solution would be to store the images as individual files on disk, using an album id to identify each image. Then you would setup an in memory cache to store the images in RAM for fast loading into your UIImageViews. The loading of the images from disk to RAM would ideally need to be done on a background thread (e.g. try performSelectorOnBackgroundThread) so that the I/O doesn't bog down the main thread (which will impact on your scrolling performance).
Addendum 1
If you're only calling -drawRect: once per cell load (as you should be), then the problem might be the scaling of the images. Scaling an image by drawing it in code using drawInRect will use CPU time, so an alternative approach is to scale the images as you receive them from the iPod library (if you need them in multiple sizes, save a version in each size you require). You may need to do this on a background thread when you import the data to avoid blocking the main (UI) thread.
One alternative thing to consider is that UIImageView may do it's scaling using Core Animation which would mean it is hardware accelerated (I'm not sure if this is actually the case, I'm just guessing). Switching to a UIImageView for the image would therefore get rid of the CPU burden of image scaling. You would have a slight increase in compositing overhead, but it might be the easiest way to get closer to "optimum" scrolling performance.
At this point your best bet is to use Instruments (previously Shark) to try and find bottlenecks in your code.

iPhone - Using NSCoder with subclass of UIImageView

I have created a subclass of UIImageView and I am handling the touches for its objects inside the subclass itself.
Now when the user is about to exit the app I want to save the state of the images. And as there are multiple transforms which might have taken place on the images (I am saving all the transforms in a dictionary), I want to be able to save these objects in applicationWillTerminate.
I am using the encodeWithCoder and initWithCoder methods. The objects are saved and loaded as expected (at least looks like it). But when I print the frame size and origin, I get some absurd values on the console.
Can someone please tell me if the frame is not saved when encodeWithCoder is called and the contents are stored in a file?
Thanks.
Why don't you serialize the UIImageView's frame along with the actual UIImageView? Just add another property in encodeWithCoder and initWithCoder for the frame. This way, you can be sure to have the correct frame if you feel something is off.
Please paste the code where you are logging the values of the UIImageView's frame. I believe you may be casting to the wrong type which will cause your values to be off.
write Dictionary content to plist by using NSUserDefault's object for key , and setobject for key method