如何与@autoreleasepool进行交互

时间:2013-07-04 08:09:41

标签: objective-c clang llvm nsautoreleasepool

基于Bavarious's answer to this SO question,如果您使用LLVM / clang构建,我会相信@autoreleasepool现在是Objective-C语言功能。

在这种情况下,如何在非ARC环境中重写以下代码以使用@autoreleasepool而不是NSAutoreleasePool

[NSAutoreleasePool addObject:anObject]; // (*)

背景:我基本上想编写一个-autorelease的自定义实现,它不会以任何方式 NSAutoreleasePool类进行交互:

@autoreleasepool {
    SomeCls *obj = [[SomeCls alloc] init];
    [obj autorelease]; // Does not go through an NSAutoreleasePool object
    // ...
}

5 个答案:

答案 0 :(得分:3)

@autoreleasepool { }是一种新的语言功能,旨在消除在函数的每个出口点消耗当前池的需要。现在,而不是写:

void f(void) {
    //Make a new pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    //Every inner scope that exits prematurely requires a drain
    if (life_is_good) {
        for (int i = 0; i < 1000; i++) {
            NSObject *obj = [[[NSObject alloc]init]autorelease];
            //breaks out of the loop, and the function, so drain the pool
            if (life_is_bad) {
                [pool drain];
                return;
            }
        }
        //Life gets bad below here, so return
        [pool drain];
        return;
    }
    //end of function requires drain.
    [pool drain];
}

您可以将整个函数包装在@autoreleasepool { }指令中,编译器将插入函数返回的正确排水:

void f(void) {
    //Make a new pool
    @autoreleasepool {
        if (life_is_good) {
            for (int i = 0; i < 1000; i++) {
                NSObject *obj = [[[NSObject alloc]init]autorelease];
                //breaks out of the loop, and the function, so drain the pool
                if (life_is_bad) {
                    //[pool drain]; auto-drain
                    return;
                }
            }
            //[pool drain]; auto-drain
            return;
        }
        //[pool drain]; auto-drain
    }
}

我不知道您为什么要放弃自动池机制,因为它为您处理巨大的工作量(页面管理,每线程池管理,及时释放对象)但是有效的方式),但是让原始的NSAutoreleasePool运行起来并不是一件容易的麻烦。任何自动释放池都需要页面管理器(malloc()realloc()),指针存储(一个简单的数组),以及某种方式来检测当前使用它的范围是否已经退出({ {1}})。一个简单的池可能只是一个线程安全的单例,但要注意你尝试使用该实现一次插入和删除多少个对象。如果您尝试为太多对象分配太多热门页面,则可能会出现段错误。

答案 1 :(得分:1)

  

基于Bavarious对这个SO问题的回答,我认为如果你用LLVM / clang构建,@ autoreleasepool现在是一个Objective-C语言功能。

正确。

  

在这种情况下,如何在非ARC环境中重写以下代码以使用@autoreleasepool而不是NSAutoreleasePool?

     

[NSAutoreleasePool addObject:anObject]; //(*)

文档: 通常您不直接调用此方法 - 而是将对象自动发送到对象。

@autoreleasepool {
 id anObject = ...;
 [anObject autorelease];
}
  

背景:我基本上想编写一个自定义的-autorelease实现,它不会以任何方式与NSAutoreleasePool类进行交互:

你甚至不能再继承NSAutoreleasePool(以有意义的方式)。除非您永远不会将该对象传递给其他可自由释放的API,否则您的对象将最终出现在当前的自动释放池中。今天的机制甚至不是基于对象的。

  

如果这是不可能的,我会接受不涉及NSAutoreleasePool的解决方案建议。

不确定您要解决的问题,但您可以使用以下方式延长生命周期:

NSMutableArray * fauxAutoreleasePool = NSMutableArray.new;
id anObject = ...;
[fauxAutoreleasePool addObject:anObject];
...
[fauxAutoreleasePool removeAllObjects]; // << explicitly drain, which also happens when the array is destroyed

当然,您可以编写自己的实现。你为什么要编写自己的东西仍然是个谜,对于生产代码不是一个好主意(@autoreleasepool效果很好)。

答案 2 :(得分:0)

@autoreleasepool块的末尾,自动释放的对象会发送release消息。

如果您想重新实现autorelease方法,则可能不希望直接向release子类发送NSObject消息。如果是这样,重新实施release以及某些特殊行为可以帮助您开展业务。

答案 3 :(得分:0)

  

背景:我基本上想写一个自定义的实现   -autorelease不与NSAutoreleasePool类交互:

是什么让您认为-autoreleaseNSAutoreleasePool互动?

只要您在iOS 5+或Mac OS X 10.7+上运行,@autoreleasepool-[NSObject autorelease]都将使用运行时自动释放池管理功能,并且与{{无关} 1}}所有(和那些OS版本上的NSAutoreleasePool只是运行时函数的包装器。)

答案 4 :(得分:0)

虽然我已经将答案标记为已接受并且当时提供了赏金,但我已经找到了我正在寻找的真正答案,并意识到我的问题不是很明确 - 主要是因为我不知道是什么问。

如何与@autoreleasepool互动:

当编译器遇到@autoreleasepool { }块时,它会自动插入两个函数调用。在块的开头,它插入C函数调用:

void *_objc_autoreleasePoolPush(void);

@autoreleasepool块结束时(包括遇到returnbreak时 - 但不是根据LLVM docs抛出异常时)编译器插入C函数调用:

_objc_autoreleasePoolPop(void *ctxt); // ctxt is Apple's label for the param

我相信void *ctxt参数是当前@autoreleasepool开始位置的指标(实际上仅在Apple的实施中有用 - 请参阅here)。无论如何,我发现在自定义实现中很容易忽略它。

-autorelease选择器:

基本上,方法是:

  • _objc_autoreleasePoolPush()中创建(如果是最外面的块)或标记(如果是内部块)自动释放池对象(在C或C ++中)。对我来说,使用Stack数据结构似乎最容易,但YMMV。
  • 确保此对象位于-autorelease选择器的范围内,或者您具有访问它的故障安全方式。
  • 将对象添加到-autorelease内的自动释放池对象(以任何可能的方式)。
  • _objc_autoreleasePoolPop(void *)中,将-release消息发送到自动释放池对象中的每个项目,直到达到_objc_autoreleasePoolPush()中设置的标记(或者到达池底部)。然后进行任何额外的必要清理。

有关线程的最终说明:

Objective-C @autoreleasepools应该是线程本地的。也就是说,每个线程都有自己的(如果它需要一个)。因此,_objc_autoreleasePoolPush()必须有某种方法来确定当前线程是否已经有一个活动的自动释放池,如果没有,则创建该对象。

类似地,这就是为什么在创建线程时,线程必须首先打开一个@autoreleasepool { }块然后才能执行任何其他操作(在Objective-C中),并且它必须做的最后一件事就是关闭@autoreleasepool块。 1}}阻止(隐式地通过breakreturn或使用明确的直通)。

最后三次观察:

  1. 如果您希望实现自己的自动释放池对象,则需要某种形式的线程本地存储来存储它。
  2. 关于线程的最后一点是每个Objective-C程序必须以@autoreleasepool { }中的main()块开头。
  3. 这就是Apple要求您使用NSThread课程和GCD而非DIY线程的原因 - 他们会为您处理所有这些事情!