问题
When I perform the following only on the main thread iref
gets autoreleased immediately:
-(void)loadImage:(ALAsset*)asset{
@autoreleasepool {
ALAssetRepresentation* rep = [asset defaultRepresentation];
CGImageRef iref = [rep fullScreenImage];
UIImage* image = [UIImage imageWithCGImage:iref
scale:[rep scale]
orientation:UIImageOrientationUp];
[self.imageView setImage:image];
}
}
But when I perform imageWithCGImage: with GCD on a background thread iref
does not get released instantly like in the first example. only after about a minute:
-(void)loadImage:(ALAsset*)asset{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
@autoreleasepool {
ALAssetRepresentation* rep = [asset defaultRepresentation];
CGImageRef iref = [rep fullScreenImage];
UIImage* image = [UIImage imageWithCGImage:iref
scale:[rep scale]
orientation:UIImageOrientationUp];
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.imageView setImage:image];
});
}
});
}
How can I make the CGImageRef
object to be released immediately?
Prior Research:
- The Leak instrument doesnt show any leaks when I record with it.
- The Allocations instruments shows that a
CGImageRef
object was allocated and is still living for about a minute after it should have been released. - If I try to manually release the
CGImageRef
object usingCGImageRelease
I get a BAD_EXEC exception after a minute when the image tries to get autoreleased. - retaining the
iref
usingCGImageRetain
and then usingCGImageRelease
to release it doesnt work. - similar questions on stackoverflow that didn't help: image loading with gcd, received memory warning in create image, memory leak when get fullscreenimage from alaseet result,
回答1:
First off, there's not a notion of an "autoreleased" CF object. You can get into situations where such a thing exists when dealing with toll-free bridged classes, but as you can see, there's a CFRetain
and a CFRelease
but no CFAutorelease
. So I think you're misconstruing the ownership of iref
. Let's trace ownership throughout this code:
-(void)loadImage:(ALAsset*)asset{
asset
is passed into this method. Its retain count is presumed to be at least 1.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
The block closure takes a retain on asset
.
@autoreleasepool {
ALAssetRepresentation* rep = [asset defaultRepresentation];
This returns you, by naming convention, an object that you don't own. It might be autoreleased, it might be a singleton/global, etc. But you don't own it and shouldn't take ownership of it by retaining it.
CGImageRef iref = [rep fullScreenImage];
Since there's not a notion of an "autoreleased" CF object, we'll presume that rep
is returning you an inner pointer to a CGImageRef
owned by rep
. You also don't own this, and shouldn't retain it. Relatedly you don't control when it will go away. A reasonable guess would be that it will live as long as rep
and a reasonable guess is that rep
will live as long as asset
so you should probably assume that iref
will live at least as long as asset
.
UIImage* image = [UIImage imageWithCGImage:iref
scale:[rep scale]
orientation:UIImageOrientationUp];
If the UIImage needs the CGImageRef to stick around, it will take a retain or make a copy to ensure that it stays alive. (Probably the latter.) The UIImage itself is autoreleased, by naming convention.
dispatch_async(dispatch_get_main_queue(), ^(void) {
This inner block closure is going to take a retain on image
(and self
). The block will be copied by libdispatch extending the life of those retains until the block is executed and itself released.
[self.imageView setImage:image];
The image view is going to take a retain (or copy) on image
if it needs to, in order to do its job.
});
The inner block is done executing. At some point in the future, libdispatch will release it, which will transitively release the retains taken by the block closure on self
and image
.
}
Your autorelease pool pops here. Anything that was implicitly retain/autoreleased should be released now.
});
The outer block is done executing. At some point in the future, libdispatch will release it, which will transitively release the retain taken by the block closure on asset
.
}
Ultimately, this method cannot control the lifetime of the CGImageRef in iref
because it never has ownership of it. The implication here is that the CGImageRef is transitively owned by asset
, so it will live at least as long as asset
. Since asset
is retained by virtue of being used in the outer block (i.e retained by the outer block's closure) and since libdispatch makes no promises about when finished blocks will be released, you effectively can't guarantee that iref
will go away any sooner than libdispatch gets around to it.
If you wanted to go to manual retain/release, and be as explicit about it as possible, you could do this:
-(void)loadImage:(ALAsset*)asset{
__block ALAsset* weakAsset = [asset retain]; // asset +1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
@autoreleasepool {
ALAssetRepresentation* rep = [weakAsset defaultRepresentation];
CGImageRef iref = [rep fullScreenImage];
UIImage* image = [[UIImage alloc] imageWithCGImage:iref
scale:[rep scale]
orientation:UIImageOrientationUp];
__block UIImage* weakImage = [image retain]; // image +1
[weakAsset release]; // asset -1
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.imageView setImage: weakImage];
[weakImage release]; // image -1
});
}
});
}
__block
prevents the block closures from retaining asset
and image
allowing you to explicitly retain/release them yourself. This will mean that of all the things you create, all will be explicitly disposed. (rep
and image
are presumably retain/autoreleased, but your pool should take care of those.) I believe this is the most explicit you can be about this, given that asset
is passed in to you, and therefore you don't control how long it lives, and it is ultimately the "owner" (as far as this scope is concerned) of the CGImageRef stored in iref
.
Hope that clarifies things a bit.
回答2:
You should retain the CGImageRef
..
CGImageRef iref = CGImageRetain([rep fullScreenImage]);
//..before [self.imageView..
CGImageRelease(iref)
The rest is just a matter of run loop, in one without GCD the image is released istanneously, in the other is managed by GCD, but is wrong anyway, someone has to take the ownership of iref.
来源:https://stackoverflow.com/questions/19540713/imagewithcgimage-gcd-memory-issue