Objective-C中块的生命周期

时间:2013-08-20 09:26:46

标签: objective-c

MyBlock getBlocks()
{    
    MyBlock myBlock = ^{
        NSLog(@"Hello World!");
    };

    return myBlock;
}

int main(int argc, const char * argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    MyBlock myBlock = getBlocks();

    myBlock();

    [pool drain];

    return 0;
}

为什么这段代码有效? myBlock应该被销毁。

顺便说一句,这段代码也有效:

NSObject *obj = [[NSObject alloc] init];

NSLog(@"%ld", [obj retainCount]);

MyBlock myBlock = ^{
    NSLog(@"Hello World!");
    NSLog(@"%ld", [obj retainCount]);
};

[obj release];

但块中的[obj retainCount]打印1而不是2,为什么?

2 个答案:

答案 0 :(得分:4)

出于性能原因,可以在堆栈上分配块,并且只有在复制时才会迁移到堆中。如果您的块没有捕获任何值,编译器也可以将其转换为一个全局块,其内存存在于某个固定的静态内存位置。

由于getBlocks块没有捕获任何内容,因此它是一个全局块,并且无法处理其内存。它没有引用计数语义,并且retain / release不会执行任何操作。它的引用始终有效,您始终可以调用它。

如果getBlocks 中的块捕获了某些值,那么它将是一个本地堆栈分配的块,并且该方法将返回对该堆栈内存的引用,该内存是当然很危险。在这种情况下,代码甚至可能仍然有效,即使它位于无主堆栈内存中,您也可以调用它(只要该内存在您调用块时没有被其他人删除)。

我相信你的第二个例子也证明了堆栈分配块的副作用。只有在第一次将块实际复制到堆中时,才会保留由堆栈分配的块捕获的对象。这不会发生在您的代码中,因此块不会保留obj

这意味着在您的示例中调用[obj release]之后,该块现在正在捕获一个悬空指针。使用ARC将为您修复所有这些混乱的细节。

有关详细信息,请参阅How blocks are implemented (and the consequences)(Cocoa With Love)。

编辑:此外,必须链接到when to use retainCount

答案 1 :(得分:3)

  

为什么这段代码有效?

第一个剪切正在工作,因为块实现没有引用周围的范围,因此它被clang配置为 全局 块。这会导致块不像通常那样在堆栈中。

  

myBlock应该被销毁。

堆栈帧(及其局部变量)不会被销毁。他们的记忆可用于进一步分配。无论如何,由于你的例子简单,你得到一个不在堆栈框架上的全局块。

如果您对周围的作用域进行了任何引用,那么您将拥有一个基于堆栈的块,而myBlock将是一个悬浮指针,指向一个拥有的内存位置,可能会导致崩溃。

  块中的

[obj retainCount]打印1而不是2,为什么?

听起来很合理。

您正在分配一个对象(保留计数:1),该块保留它(保留计数:2),您正在释放它(保留计数:1),该块最终被执行(保留计数仍为1)。

总的来说,无论如何,在推理记忆时不要依赖retainCount。内存管理过程有很多内容,你只能通过查看retainCount的值来明确说出任何内容,因为您无法考虑实现的内部细节。有关此主题的更多信息,请访问:http://whentouseretaincount.com/