imageWithCGImage: GCD memory issue

梦想与她 提交于 2019-12-13 00:48:10

问题


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 using CGImageRelease I get a BAD_EXEC exception after a minute when the image tries to get autoreleased.
  • retaining the iref using CGImageRetain and then using CGImageRelease 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!