即使使用间接的__block引用,我似乎也在块中保留self,这可能是因为将块复制到堆中?

时间:2012-10-03 03:12:00

标签: cocoa-touch cocoa objective-c-blocks

[编辑:由于引起混淆,整个案例假设MRR而不是ARC]

我有一个奇怪的(显然有一个解释,我只是无法弄清楚)一个引用自我(间接)的块的行为,并被转移到另一个对象的属性(即从对象复制)作为'堆栈到堆并由对象B保留)。如果块不包含对_this的引用,则每次从导航控制器弹出对象A的dealloc时都会调用它。但是,如果块引用_this,那么对象的(下面的代码中的MyObjectA)dealloc只会每隔一次调用一次。也就是说,我推这个视图控制器子类,调用createBlock,我弹出并没有任何反应。我再次推送,再次调用createBlock,然后弹出然后它在MyObjectA上调用dealloc。

为了简洁起见,我只发布我认为对行为至关重要的片段。

假设我有一个对象MyObjectA(自定义UIViewController的sublcass),它包含一个方法createBlock,如下所示:

- (void)createBlock
{
  __block MyObjectA* _this = self;
  int(^animationBlock)(NSArray*,NSDictionary*);


  animationBlock =
  ^(NSArray* layers, NSDictionary* parameters)
  {
    [CATransaction begin];
    [CATransaction setCompletionBlock:
     ^{
       for(CALayer* layer in layers)
         layer.opacity = 1;
     }];

    CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
    a2.fromValue = [NSNumber numberWithFloat:0.];
    a2.toValue = [NSNumber numberWithFloat:1.];
    a2.duration = .4;
    a2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    a2.fillMode = kCAFillModeBoth;
    a2.removedOnCompletion = NO;

    CABasicAnimation* a = [CABasicAnimation animationWithKeyPath:@"position.x"];
    a.duration = .4;
    a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    a.fillMode = kCAFillModeBoth;
    a.removedOnCompletion = NO;

    CAAnimationGroup* g = [CAAnimationGroup animation];
    g.animations = [NSArray arrayWithObjects:a,a2, nil];
    g.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    g.fillMode = kCAFillModeBoth;
    g.removedOnCompletion = NO;

    CALayer* numberLayer;
    CALayer* flechaLayer;
    CGFloat timeOffset = 0;
    for(int i = 0; i < [layers count]; i+=2)
    {
      numberLayer = [layers objectAtIndex:i];
      flechaLayer = [layers objectAtIndex:i+1];

      a2.beginTime = [_this.view.layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset;
      [numberLayer addAnimation:a2 forKey:nil];

      a2.beginTime = 0;
      a.fromValue = [NSNumber numberWithFloat:flechaLayer.frame.origin.x + 100];
      a.toValue = [NSNumber numberWithFloat:flechaLayer.frame.origin.x + flechaLayer.frame.size.width / 2.];
      g.duration = 3;
      g.beginTime = [_this.view.layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset + .4;
      [flechaLayer addAnimation:g forKey:nil];

      timeOffset += 1.5;
    }

    [CATransaction commit];

    return 0;
  };

  [[AnimationFactory sharedFactory] registerAnimationBlock:animationBlock forKey:@"EnsureFlechasNutricion"];
}

如您所见,动画块中有_this的引用。 然后,注册块的AnimationFactory(单例)方法是:

- (void)registerAnimationBlock:(int(^)(NSArray*, NSDictionary*))animationBlock forKey:(NSString*)key
{
  int(^heapBlock)(NSArray*, NSDictionary*) = [animationBlock copy];
  [self.animationBlocks setObject:heapBlock forKey:key];
  [heapBlock release];
}

我的猜测是将块复制到堆中是保留MyObjectA,或者可能将块添加到AnimationFactory中的NSMutableDictionary中......但我不确定。 有什么想法吗?

2 个答案:

答案 0 :(得分:0)

[更新:这个答案在使用ARC时适用,从评论中看出MRC正在使用,所以它不是答案!]

__block属性适用于需要可由块更新的变量的情况,即变量通过引用而不是按值<传递给块 / em>这是默认情况。您的代码中似乎不需要这样做,您不会在块中更新_this的值。

要打破强参考周期,请使用__weak属性。您当前的_this是对self引用的同一对象的强引用,因此您的数据块会以强引用结束。

答案 1 :(得分:0)

好吧我明白了:当我将新复制的(堆)块添加到AnimationFactory的字典中时,即使在最初在自己声明块时执行弱引用shebang,我也必须保留自我。

解决方案是获取一个弱(也就是__block Class * identifier = eval,因为我不在ARC上)引用self.view,这就是我引用self开始的原因,而不是一个自我启动。这样,尽管此视图的引用计数增加,但自我引用计数保持正确。然后,在pop上,自动不会被AnimationFactory的字典保留并调用dealloc。

我应该提到self的dealloc包含对另一个方法的调用,该方法又从AnimationFactory中删除所有已注册的块,使self.view的保留计数过于隐含地恢复正常,从而不会泄漏。