【问题标题】:What's the right memory management pattern for buffer->CGImageRef->UIImage?buffer->CGImageRef->UIImage 的正确内存管理模式是什么?
【发布时间】:2011-01-09 09:27:04
【问题描述】:

我有一个函数,它接受一些位图数据并从中返回一个 UIImage *。它看起来像这样:

UIImage * makeAnImage() 
{
    unsigned char * pixels = malloc(...);
    // ...
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, pixelBufferSize, NULL);
    CGImageRef imageRef = CGImageCreate(..., provider, ...);
    UIImage * image =  [[UIImage alloc] initWithCGImage:imageRef];
    return [image autorelease];
}

谁能在这里准确地解释谁拥有什么内存?我想正确清理,但我不确定如何安全地清理。文档对这些内容很模糊。如果我在创建 UIImage 后在此函数末尾 free 像素,然后使用 UIImage,我会崩溃。如果我在创建 UIImage 后释放提供程序或 imageRef,我不会看到崩溃,但它们显然是一路传递像素,所以我对释放这些中间状态感到不安。

(根据 CF 文档,我知道我应该对后者调用 release,因为它们来自 Create 函数,但我可以在使用 UIImage 之前这样做吗?)大概我可以使用提供者的 dealloc 回调来清理像素缓冲区,但还有什么?

谢谢!

【问题讨论】:

    标签: iphone uiimage core-graphics quartz-2d core-foundation


    【解决方案1】:
    unsigned char * pixels = malloc(...);
    

    在使用 CGImageCreate 后我也遇到了 malloc/free 的问题 我终于找到了好的简单的解决方案。 我只是替换行:

    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, pixelBufferSize, NULL);
    

    与:

    NSData *data = [NSData dataWithBytes:pixels length:pixelBufferSize];
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
    

    在那之后我可以释放锁定的内存:

    free (pixels);
    

    【讨论】:

    • 这需要复制所有字节,这对于某些用例可能效率低下。相反,使用 [NSData dataWithBytesNoCopy:length] 将获取字节的所有权,并在不再需要它们时为您释放它们。
    【解决方案2】:
    unsigned char * pixels = malloc(...);
    

    您拥有 pixels 缓冲区,因为您将其锁定。

    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, pixelBufferSize, NULL);
    

    Core Graphics 遵循 Core Foundation 规则。您拥有数据提供者,因为您 Created 它。

    您没有提供释放回调,因此您仍然拥有pixels 缓冲区。如果您提供了释放回调,则 CGDataProvider 对象将在此处取得缓冲区的所有权。 (通常是个好主意。)

    CGImageRef imageRef = CGImageCreate(..., provider, ...);
    

    您拥有 CGImage 对象,因为您创建了它。

    UIImage * image =  [[UIImage alloc] initWithCGImage:imageRef];
    

    您拥有 UIImage 对象,因为您分配了它。

    您还拥有 CGImage 对象。如果 UIImage 对象想要拥有 CGImage 对象,它将保留它或制作自己的副本。

    return [image autorelease];
    

    您放弃了对该图像的所有权。

    所以您的代码泄露了像素(您没有将所有权转让给数据提供者,也没有您自己发布它们)、数据提供者(您没有发布它)和 CGI​​mage(您没有释放它)。固定版本会将像素的所有权转移给数据提供者,并在 UIImage 准备好时释放数据提供者和 CGI​​mage。或者,按照 KennyTM 的建议,使用 imageWithData:

    【讨论】:

    • Peter:感谢您抽出宝贵的时间来了解这一点,尤其是在问答会结束后。
    【解决方案3】:

    这里的经验法则是“-release* 如果你不需要它”。

    因为之后你不再需要providerimageRef,你应该-release所有这些,即

    UIImage * image =  [[UIImage alloc] initWithCGImage:imageRef];
    CGDataProviderRelease(provider);
    CGImageRelease(imageRef);
    return [image autorelease];
    

    pixel 不通过引用计数来管理,因此您需要告诉 CG API 在必要时为您释放它们。这样做:

    void releasePixels(void *info, const void *data, size_t size) {
       free((void*)data);
    }
    ....
    
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, pixelBufferSize, releasePixels);
    

    顺便说一句,您可以使用+imageWithCGImage: 代替[[[* alloc] initWithCGImage:] autorelease]。更棒的是,还有+imageWithData:,所以你不需要弄乱CG和malloc的东西。

    (*: 除非retainCount 从一开始就应该是零。)

    【讨论】:

    • 谢谢肯尼。这是一个非常简洁的描述;我想我对原始堆缓冲区的不可预测性有点失望,但一如既往,相信规则,你会得到回报。干杯。
    • 只是为了澄清一下,核心函数中的关键词是“创建”和“新建”。如果函数包含这些单词中的任何一个,则必须释放返回的内存。大多数核心数据类型都与 CFType 兼容。这意味着如果更容易,您可以对它们使用 Objective-C 保留/释放/自动释放调用。即 [(id)imageRef 发布];或 CFRelease(imageRef);
    • 如果你使用CFRelease,记得检查imageRef是不是NULL
    【解决方案4】:

    是的,这段代码让我感到恶心。作为一个老规矩,我尽量不要在同一个函数/方法/选择器中混合和匹配 C 和 C++,以及 C/Objective-C。

    如何将其分解为两种方法。已将此 makeAnImage 更改为 makeAnImageRef 并将 UIImage 创建拉到另一个 Obj-C 选择器中。

    【讨论】:

    • 虽然我理解反应:) 这样做仍然无助于我的内存管理问题。我仍然有一个问题,将原始字节传递到管道的开头给我留下了一个 UIImage ,不知何故仍然需要这些字节。所以这段代码比看起来更相关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 2012-03-26
    相关资源
    最近更新 更多