在ARC下使用接收块的方法的对象生命周期

时间:2014-03-04 17:20:14

标签: ios objective-c block objective-c-blocks

现在我有这样的事情:

- (void)viewDidLoad
{
    MyObject *myObject = nil;
    @autoreleasepool
    {
        myObject = [[MyObject alloc] init];

        [myObject doSomethingWithBlock:^{
            NSLog(@"Something Happened");
        }];
    }

    NSLog(@"End of method");
}

doSomethingWithBlock:有以下内容:

- (void)doSomethingWithBlock:(void(^)())aBlock
{
    [self performSelector:@selector(something:) withObject:aBlock afterDelay:4.0f];
}

something:

- (void)something:(void(^)())aBlock {
    aBlock();
 }

据我所知,该块正在堆栈帧之间传递,所以它一直处于活动状态,直到它实际被执行并被处理掉。但是为什么当调用“方法结束”时myObjct仍然存在?我知道我引用块内的对象,所以它不应该在自动释放池中释放吗?或者这是一个编译器细节(如果它实际上应该在那里释放,或者在方法返回后),我不应该关心它?


有人指出performSelector会保留目标,所以我改用了它:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    aBlock();
});

2 个答案:

答案 0 :(得分:1)

我不清楚问题是关于块的生命周期,根据标题,还是myObject引用的对象的生命周期,因此我们将涵盖两者。

首先,优化中的块的堆栈分配和程序员不应该真正意识到。不幸的是,当块首次引入时,编译器不够智能,无法完全自动处理这种优化,因此需要了解Block_copy()等。但编译器现在更加智能,程序员几乎可以忘记堆栈分配块。

这个答案适用于Xcode 5.0.2 / Clang 4.2。使用其他(早期)版本和YMMV。

第二,块:

^{ NSLog(@"Something Happened"); }

实际上根本没有分配堆栈。因为它没有引用环境中的值,所以它是静态分配的,永远不需要放在堆栈上或移动到堆 - 另一个优化。要获得堆栈分配块,只需将其更改为:

^{ NSLog(@"Something Happened: %p", self); }

将分配堆栈(并且不用担心可能的保留周期,这是另一个主题......)

有了这个,让我们来看看生命周期:

对象:

变量myObject的生命周期最多为viewDidLoad的调用,变量是在方法入口处创建的,如果精确的生命周期语义生效则在退出时销毁,如果是,则更早不需要和精确的生命周期语义不起作用 - 后者是默认允许编译器优化存储使用(独立于ARC)。默认情况下,局部变量具有强大的所有权限定符,因此对存储在其中的任何引用都声明所有者权益。当另一个引用存储在变量中或者变量本身被销毁时,ARC将释放该所有者权益。后者将在这种情况下发生,并且变量在最后一次使用它和方法结束后的某个时间被销毁 - 取决于编译器如何进行优化。

所有这些意味着myObject引用的对象可能会或可能不会在您NSLog()的呼叫时出现。在使用performSelector:withObject:afterDelay:的情况下,调用将断言对象的所有权,直到执行选择器之后 - =所以对象将存活。在使用dispatch_after的情况下,对象不是必需的,因此不会保持活着。

阻止:

如上所述,如上所述,块是静态分配的,因此生命周期很容易 - 它始终存在。更有趣的是如上所述修改它以使其分配堆栈。

如果块是堆栈分配的,它将存在于viewDidLoad调用的堆栈帧中,除非移动或复制到堆中,否则当该调用返回时它将消失。

doSomethingWithBlock:的调用将传递对此堆栈分配块的引用。这是可能的,因为doSomethingWithBlock:期望其中的块和代码可以在需要时将其移动到堆,因为它知道它是块。

现在它变得有趣了。在doSomethingWithBlock:情况下的performSelector:withObject:afterDelay:内,块作为withObject:类型的id参数传递 - 这意味着被调用的代码需要一个标准的Objective-C对象和那些住在堆里。这是类型信息丢失的情况,调用者有一个块,被调用者只看到id。所以在这一点上,编译器插入代码将块复制到堆中并将新的堆块传递给performSelector:withObject:afterDelay: - 它将其视为任何其他对象。 [较旧的编译器并不总是这样做,程序员必须知道类型信息丢失即将发生并手动插入代码以将块移动到堆中。]

dispatch_after的情况下,函数参数是块类型的,因此编译器只传递堆栈分配块。但是,dispatch_after函数根据其文档将块复制到堆中,并且没有问题。

因此,在任何一种情况下,在 viewDidLoad完成之前,堆栈分配的块被复制到堆,并且它的堆栈帧被销毁,并带有堆栈分配块。

HTH。

答案 1 :(得分:0)

它在自动释放池之外声明,因此它应该在它之后存在。它是关于声明对象的范围,而不是分配的范围。通过您的逻辑类,在自动释放池中分配的变量应该在池的末尾为零,并且在其他地方不可用。

实施例

-(void)function
{
    MyObject *object;  //MyObject declared here
    if(someBoolean)
    {
        object = [[MyObject alloc] init];  //MyObject allocated here
    }

    //This is outside the scope of allocation, 
    //but it's still inside the scope of declaration.
    //If ARC referenced the scope of allocation your 
    //object would not be valid here, as ARC would 
    //release it as out of scope.
}