使用CGDataProviderCreateWithData回调函数

4

我正在使用CGDataProviderCreateWithData函数,通过一个malloc分配的字节数组最终创建UIImage。我调用CGDataProviderCreateWithData的方式如下:

 provider = CGDataProviderCreateWithData(NULL, dataPtr, dataLen, callbackFunc);

这里的dataPtr是之前为图像分配的数据字节数组,dataLen是dataPtr数组中字节的数量,callbackFunc与CGDataProviderCreateWithData文档中描述的一样:

 void callbackFunc(void *info, const void *data, size_t size);

回调函数在数据提供程序释放时被调用,所以我可以在那里释放dataPtr,但我可能希望在稍后的某个阶段继续使用它(dataPtr)并最终释放它。这段代码将被多次调用,并且流程看起来像:
  1. malloc(dataPtr)
  2. 创建图像(调用CGDataProviderCreateWithData等)
  3. 显示图像
  4. 释放图像(以及由CGDataProviderCreateWithData创建的数据提供程序)
  5. 继续使用dataPtr
  6. free(dataPtr)
因此1...6可能会被执行多次。我不想让dataPtr在整个程序执行期间挂起(而且它的大小可能会改变),所以我想根据需要进行malloc/free。
问题是我不能在来自CGDataProviderCreateWithData的回调中使用free(dataPtr),因为我仍然想使用它,所以我想在稍后的某个时间释放它,但我不能在不知道数据提供程序是否还需要它的情况下进行释放(据我所知,CGDataProviderCreateWithData使用我传递的数组,它不会复制)。
我无法执行上面的步骤直到确定可以安全地释放和重新分配dataPtr,因此我真正想做的是阻止等待数据提供程序被释放(好吧,我想知道是否应该重新进入1...6的代码块,而在数据提供程序被释放之前我无法这样做)。这将会发生 - 我创建数据提供程序,创建图像,立即显示它并释放数据提供程序。问题是,直到UIImage释放并完成对它的使用为止,数据提供程序才实际被释放。
我相当新于Objective-C和iOS,请问我有没有漏掉什么显而易见的东西?
3个回答

1
如果你使用 malloc 来分配提供程序数据的内存,则确实需要在回调中使用 free 释放它。不要试图用任何方式规避此问题。这样很容易泄漏,也容易受到简单的内存问题的影响。
话虽如此,有两种简单的解决方案可以解决您的问题。要么:
  • 复制您将单独管理的数据的副本;或者

  • 不使用 CGDataProvider 方法的 void * 版本,而是使用 CFData 版本(例如 CGDataProviderCreateWithCFData),然后您可以维护自己的对该数据对象的强引用(或者如果在非 ARC 代码中,则执行自己的 retain 操作)。直到所有强引用都解决(或者在非 ARC 代码中,所有手动 retain 调用都通过相应的 releaseautorelease 调用解决)之前,该对象将不会被释放。

通过这两种方法,您可以继续让CGProviderRef按照其自身的方式管理内存,但您也可以继续将数据对象用于自己的目的。


0

我也遇到了类似的问题。我想使用CGImageCreateWithJPEGDataProvider,所以使用了一个malloc分配的字节数组来创建CGDataProviderRef。在创建数据提供程序引用后的某个时刻尝试释放字节数组时,我遇到了相同的问题。 这是因为数据提供程序引用接管了字节数组的所有权,因此只能在引用完成后由引用释放。 如果您需要在其他地方使用数据,我同意@TheBlack的观点,应该复制一份。


-1

我所做的基本上与你想要实现的相同,只是我采取了不同的方法。

整个过程都被封装成NSOperation,因此您可以控制调度内存使用情况。

CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);

NSUInteger bitsPerComponent = CGImageGetBitsPerComponent(imageRef);


UInt8 *rawData = calloc((height * width * bitsPerComponent), sizeof(UInt8));

NSUInteger bytesPerRow = CGImageGetBytesPerRow(imageRef);


CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                            bitsPerComponent, bytesPerRow, colorSpace,
                                            kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);

if (context)
{   

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); 
            // This is the place you get access to data trough rawData. Do whatever you want with it, when done, get image with 
 CGImageRef newImg = CGBitmapContextCreateImage(context);
 }

子类化CALayer,重写drawInContext并使用CGBitmapContextGetData获取原始数据。虽然我对此没有太多经验,但如果您想根据用户输入实时更改图像,我会这样做:

子类化UIView和CALayer,将其作为视图的层,并显示图像。视图在层类中获取输入和图像数据(从CGBitmapContextGetData),并根据输入操作。即使是对于巨大的图像,也可以使用CATiledLayer以这种方式进行操作。完成图像后,只需释放UIView并用新的替换它。如果您需要任何帮助,我很乐意提供帮助,请在此处联系我。


但是我不是已经拥有它了吗,因为我已经使用malloc分配了它?我已经拥有所有权 - 我不会让回调函数释放它 - 我只想知道何时可以自由释放它(即当数据提供程序完成对其的操作时)。 - JeffR
啊,但如果我创建一个副本,那么我的应用程序的内存使用量会相应增加 - 如果这是一张大图像,我就有两份数据字节的副本在内存中... 我不能等到数据提供者处理完它吗? - JeffR
您也拥有CGDataProvider的所有权,因为您使用CGDataProviderCreateWithData创建了它,并且需要通过CGDataProviderRelease负责其处理。我在我的答案中告诉了您如何创建图像数据的副本,该副本可以与创建图像、显示、释放数据提供程序等过程分开使用。请问为什么您要按照1-6步骤的顺序进行操作?是否涉及图像操作? - TheBlack
抱歉 - 是的,有图像处理。因此,我可能会多次更改dataPtr数组并重新显示图像。然后,在完成这些操作之后,我可能想要加载一个新图像并重新开始1..6的过程。 - JeffR
我拥有数据提供程序并发布它,但回调可能要稍后一点时间才会发生(异步)-因此,如果我在释放数据提供程序后立即释放dataPtr,则应用程序偶尔会崩溃。我添加了诊断NSLogs,并发现回调可能会在执行其他代码之后发生-因此,在提供程序实际完成使用dataPtr之前,我可能已经释放了它。 - JeffR
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接