如何在ARC下自动释放未返回的对象

时间:2013-01-06 20:19:16

标签: objective-c memory-management automatic-ref-counting foundation

考虑像这样的函数:

void* getData()
{
    void* data= malloc(32);
    NSData* __autoreleasing dataObject= [NSData dataWithBytesNoCopy: data length: 32 freeWhenDone: YES]
    return data;
}

如果我尝试执行此代码并在返回的内存区域中打印值(虽然它们没有初始化,但只是为了进行测试),我没有任何异常。

但是如果我尝试设置断点并查看生命对象的地图,虽然数据仍在堆中,但是没有NSData对象存活,为什么?

我会知道如何返回一个自动释放的对象,就像ARC之前一样。 ARC处理所有内容,但在这种情况下,数据被释放,因为我退出了函数范围。如何在通话后让它活着并自动释放?

2 个答案:

答案 0 :(得分:3)

这是不可能的。 ARC旨在自动释放无法访问的对象。由于NSData对象是本地的而且没有返回,因此无法使用它,因此ARC将其解除分配。

根据您发布的代码判断,您似乎想要返回指向某些数据的指针,这些数据将自动释放,即使它不是Objective-c对象。您可能还希望能够使用生成它的函数内部的NSData API来访问此数据。你有几个选择。

  1. 更改代码以返回NSData对象。如果调用代码想要直接访问缓冲区,则可以使用NSData的bytes方法。但是,如果从C调用代码,则无法执行此操作。

  2. 停止尝试自动释放C数据。好的C代码应该知道何时需要释放数据,所以这应该不是问题。只需将代码更改为freeWhenDone: NO,并在完成数据后让调用代码使用free()

  3. 将此函数放在自己的文件中,并为该文件禁用ARC。这允许您通过手动调用autorelease来执行您想要的操作,但您还必须手动处理函数其余部分的引用计数。

  4. 组合1和3.有一个使用ARC的函数并返回NSData对象,而没有ARC的包装函数调用第一个获取自动释放的对象,然后从结果中返回缓冲区指针。

答案 1 :(得分:2)

一种方法是添加第二个功能

NSData *autoreleaseBufferOfLength(void *bytes, NSUInteger length) 
{ 
    return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]; 
}  

此函数将返回一个自动释放的NSData对象。

从缓冲区返回函数

中调用此函数
void *getAutoreleasedBuffer(NSUInteger length)
{
    void *buffer = malloc(length);
    if (buffer) {
        autoreleaseBufferOfLength(buffer, length);
    }
    return buffer;
}

NSData返回的autoreleaseBufferOfLength对象位于自动释放池中,因此在自动释放池耗尽时将释放缓冲区(因为在NSData中创建的autoreleaseBufferOfLength对象然后解除分配。)

我使用此main函数

测试进行调试
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        __unused void * buffer = autoreleasingBuffer(32);

        NSLog(@"%s:exiting @autoreleasepool block", __PRETTY_FUNCTION__);
    }
    NSLog(@"%s:exited @autoreleasepool block", __PRETTY_FUNCTION__);
    return 0;
}

并在-[NSConcreteData dealloc]处添加带有操作po @"deallocating NSConcreteData"的符号断点,并选中“评估后自动继续”。这是输出

[53030:303] int main(int, const char **):exiting @autoreleasepool block
(NSString *) $0 = 0x0000000100300530 deallocating NSConcreteData
[53030:303] int main(int, const char **):exited @autoreleasepool block

然后我测试了发布,稍微更改了测试,添加了__weak全局变量g_data,该变量在autoreleaseBufferOfLength中设置,

__weak NSData *g_data = nil;
NSData *autoreleaseBufferOfLength(void *bytes, NSUInteger length) 
{ 
    return (g_data = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]); 
}  

并更改main中的日志记录:

int main(int argc, const char * argv[])
{
    @autoreleasepool 
    {
        __unused void * buffer = autoreleasingBuffer(32);

        NSLog(@"%s:exiting @autoreleasepool block; g_data = %@", __PRETTY_FUNCTION__, g_data);
    }
    NSLog(@"%s:exited @autoreleasepool block; g_data = %@", __PRETTY_FUNCTION__, g_data);
    return 0;
}

在构建发布和运行时,这是输出:

[53934:707] int main(int, const char **):exiting @autoreleasepool block; g_data = 00000000 00000070 00000000 00000070 10000000 00000000 00000000 00000000>
[53934:707] int main(int, const char **):exited @autoreleasepool block; g_data = (null)

这些测试表明在第二个函数dataWithBytesNoCopy:length:freeWhenDone:中包含对autoreleaseBufferOfLength的调用会产生创建NSData对象并将其放入自动释放池的效果。

注意:这是 NOT 一个好主意。通过自动释放池的耗尽实现malloc'd free'的最佳方法是添加一个没有ARC(使用-fno-objc-arc)构建的文件,如@ ughoavgfhw在他的回答中描述。然而,这种方法可能有一些兴趣。