tags:

views:

529

answers:

4

Hi,

Up to OS 3.2, I used this kind of code to load UIImageView image in background, and it worked fine...

Code:

- (void)decodeImageName:(NSString *)name
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    UIImage *newImage = [UIImage imageNamed:name];
    [myImageView setImage:newImage];
    [pool release];
}
...
[self performSelectorInBackground:@selector(decodeImageName:) withObject:@"ID"]

... even if [UIImageView setImage:] was not thread-safe !

But since OS 4, it doesn't work any more... Images appear on screen two seconds after setImage call. And if I do a [myImageView performSelectorOnMainThread:@selector(setImage:) withObject:newImage waitUntilDone:YES] instead of [myImageView setImage:newImage], images appear immediately but seem to be re-decoded again on-the-fly (ignoring the previous [UIImage imageNamed:] which should have already decoded the image data), causing a pause on my main thread... Even if documentation says "The underlying image cache is shared among all threads.".

Any thought ?

A: 

performSelectorInBackground: runs a selector in a background thread. Yet setImage: is a UI function. UI functions should only be run on the main thread. I do not have insight into the particular problem, but this is the first gut feel about this code, and it may be that iOS4 handles the (non-supported) mechanism of running UI functions in background threads somehow differently.

Jaanus
+1  A: 

Don’t do it in the background! It’s not thread-safe. Since an UIImageView is also an NSObject, I think that using -[performSelectorOnMainThread:withObject:waitUntilDone:] on it might work, like:

[myImageView performSelectorOnMainThread:@selector(setImage:) withObject:newImage waitUntilDone:NO];

And it’s UIImage which is newly made thread-safe. UIImageView is still not thread-safe.

Evadne Wu
A: 

If you're using iOS 4.0, you should really consider reading up on blocks and GCD. Using those technologies, you can simply replace your method with:

- (void)decodeImageName:(NSString *)name
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    UIImage *newImage = [UIImage imageNamed:name];

    dispatch_async(dispatch_get_main_queue(), ^{
        [myImageView setImage:newImage];
    }

    [pool release];
}
jenningj
It doesn't help much. The dispatch_async still pauses the main thread longer than what it should be as if the uiimage was re-decoded. How can I be sure the UIImage is decoded after [UIImage imageNamed:name] ?
Damien
I would recommend supporting OS 3.1.3 for at least several months, and 3.2 until a few months after 4.0+ for iPad is released.
tc.
A: 

Let's quote:

@property(nonatomic, readonly) CGImageRef CGImage

Discussion

If the image data has been purged because of memory constraints, invoking this method forces that data to be loaded back into memory. Reloading the image data may incur a performance penalty.

So you might be able to just call image.CGImage. I don't think CGImages are lazy.

If that doesn't work, you can force a render with something like

// Possibly only safe in the main thread...
UIGraphicsBeginImageContext((CGSize){1,1});
[image drawInRect:(CGRect){1,1}];
UIGraphicsEndImageContext();

Some people warn about thread-safety. The docs say UIGraphics{Push,Pop,GetCurrent}Context() are main-thread-only but don't mention anything about UIGraphicsBeginImageContext(). If you're worried, use CGBitmapContextCreate and CGContextDrawImage.

tc.