1

当我仅在主线程上执行以下操作时,iref会立即自动释放:

-(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];
    }
}

但是当我在后台线程上使用 GCD 执行 imageWithCGImage: 时,iref不会像第一个示例中那样立即释放。大约一分钟后:

-(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];
            });
        }
    });
}

如何使CGImageRef对象立即释放?

先前的研究:

  • 当我用它记录时,泄漏仪器没有显示任何泄漏。
  • Allocations 工具显示一个CGImageRef对象已被分配,并且在它应该被释放后仍然存在大约一分钟。
  • 如果我尝试使用手动释放CGImageRef对象,CGImageRelease则在一分钟后图像尝试自动释放时会出现 BAD_EXEC 异常。
  • 保留iref使用CGImageRetain然后使用CGImageRelease释放它不起作用。
  • 关于stackoverflow的类似问题没有帮助:使用gcd加载图像,在创建图像时收到内存警告,从alaseet结果获取全屏图像时内存泄漏,
4

2 回答 2

10

首先,没有“自动释放”CF 对象的概念。在处理免费桥接课程时,您可能会遇到这种情况,但正如您所见,有 aCFRetain和 aCFRelease但没有CFAutorelease。所以我认为你误解了iref. 让我们在整个代码中跟踪所有权:

-(void)loadImage:(ALAsset*)asset{

asset被传递到这个方法中。假定其保留计数至少为 1。

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {

块闭包需要保留asset

        @autoreleasepool {
            ALAssetRepresentation* rep = [asset defaultRepresentation];

通过命名约定,这将返回一个您不拥有的对象。它可能是自动发布的,它可能是单例/全局的,等等。但你不拥有它,也不应该通过保留它来获得它的所有权。

            CGImageRef iref = [rep fullScreenImage];

由于没有“自动释放” CF 对象的概念,我们假设它rep会返回一个指向CGImageRef所拥有的内部指针rep。你也不拥有它,也不应该保留它。相关地,您无法控制它何时会消失。一个合理的猜测是它会活多久rep,一个合理的猜测是它rep会活多久,所以asset你可能应该假设它iref至少活asset

            UIImage* image = [UIImage imageWithCGImage:iref
                                                 scale:[rep scale]
                                           orientation:UIImageOrientationUp];

如果 UIImage 需要 CGImageRef 保持不变,它将进行保留或复制以确保它保持活动状态。(可能是后者。)根据命名约定,UIImage 本身是自动释放的。

            dispatch_async(dispatch_get_main_queue(), ^(void) {

这个内部块闭包将保留image(和self)。该块将由 libdispatch 复制,以延长这些保留的寿命,直到该块被执行并自行释放。

                [self.imageView setImage:image];

如果需要,图像视图将保留(或复制)image,以完成其工作。

            });

内部块执行完毕。在未来的某个时候,libdispatch 将释放它,这将传递地释放块闭包在selfand上的保留image

        }

你的自动释放池弹出在这里。任何被隐式保留/自动释放的东西现在都应该被释放。

    });

外部块已完成执行。在未来的某个时候,libdispatch 将释放它,这将传递地释放块关闭所占用的保留asset

}

最终,此方法无法控制 CGImageRef 的生命周期,iref因为它从未拥有它。这里的含义是 CGImageRef 由 传递拥有asset,因此它的寿命至少与asset. 由于由于asset在外部块中使用而被保留(即由外部块的闭包保留)并且由于 libdispatch 没有承诺何时释放已完成的块,因此您实际上不能保证它iref会在 libdispatch 获得之前消失围绕它。

如果您想手动保留/释放,并尽可能明确,您可以这样做:

-(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防止块闭包保留assetimage允许您自己显式保留/释放它们。这意味着在您创建的所有事物中,所有事物都将被显式处置。(rep并且image可能是保留/自动释放,但您的池应该处理这些。)我相信这是您可以对此最明确的,因为它asset已传递给您,因此您无法控制它的寿命,它最终是 CGImageRef 存储在iref.

希望能澄清一点。

于 2013-10-23T15:22:37.593 回答
2

你应该保留 CGImageRef
..

CGImageRef iref = CGImageRetain([rep fullScreenImage]);
//..before [self.imageView..
CGImageRelease(iref)

剩下的只是一个运行循环的问题,一个没有 GCD 镜像是即时发布的,另一个是由 GCD 管理的,但无论如何都是错误的,必须有人获得 iref 的所有权。

于 2013-10-23T12:29:15.307 回答