【问题标题】:Instruments is reporting I'm leaking NSMallocBlocks (aka block object) but am I really?Instruments 报告说我正在泄漏 NSMallocBlocks(又名块对象),但我真的是这样吗?
【发布时间】:2012-05-19 10:27:38
【问题描述】:

我正在开发一个应用程序,在该应用程序中,向导航控制器推送和弹出的不同视图控制器可以注册 CoreAnimation 序列,然后在推送控制器的整个生命周期中的不同时间触发这些序列。

Instruments 报告说,每次我推送控制器时,都会泄漏块对象(每次推送时,每个动画块都会泄漏 32 字节)。但是,我看不出我在哪里泄漏。这是相关代码:

有一个单例 AnimationFactory,其中包括这个方法:

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

然后,不同的视图控制器,当被推送到导航控制器的堆栈时,使用如下代码注册它们不同的动画序列,例如:

- (void)setupCommonAnimations
{
  int(^animationBlock)(NSArray*,NSDictionary*);

  /************************************************************************************************************************/
  //  Move stuff up
  /************************************************************************************************************************/
  animationBlock =
  ^(NSArray* layers, NSDictionary* parameters)
  {
    CGFloat timeOffset = [[parameters objectForKey:@"timeOffset"] floatValue];
    CABasicAnimation* a;

    CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
    a2.fromValue = [NSNumber numberWithFloat:0.];
    a2.toValue = [NSNumber numberWithFloat:1.];

    CAAnimationGroup* g = [CAAnimationGroup animation];
    g.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    g.fillMode = kCAFillModeBoth;
    g.removedOnCompletion = NO;
    g.duration = .4;

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

    for(CALayer *layer in layers)
    {
      a = [CABasicAnimation animationWithKeyPath:@"position.y"];
      a.fromValue = [NSNumber numberWithFloat:1024 + layer.frame.size.height / 2];
      a.toValue = [NSNumber numberWithFloat:layer.frame.origin.y + layer.frame.size.height / 2];

      g.animations = [NSArray arrayWithObjects:a,a2, nil];
      g.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset;

      [layer addAnimation:g forKey:nil];

      timeOffset += .2;
    }

    [CATransaction commit];

    return 0;
  };

  [[AnimationFactory sharedFactory] registerAnimationBlock:animationBlock forKey:@"StuffUpAnimation"];

/************************************************************************************************************************/
  //  Sequential Fade-in
  /************************************************************************************************************************/
  animationBlock =
  ^(NSArray* layers, NSDictionary* parameters)
  {
    CGFloat timeOffset = [[parameters objectForKey:@"timeOffset"] floatValue];
    CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
    a2.fromValue = [NSNumber numberWithFloat:0.];
    a2.toValue = [NSNumber numberWithFloat:1.];
    a2.duration = .4;
    a2.fillMode = kCAFillModeBoth;
    a2.removedOnCompletion = NO;

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

    for(CALayer *layer in layers)
    {
      a2.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset;
      [layer addAnimation:a2 forKey:nil];
      timeOffset += .4;
    }

    [CATransaction commit];

    return 0;
  };

  [[AnimationFactory sharedFactory] registerAnimationBlock:animationBlock forKey:@"SeqFadeInAnimation"];

例如,上面的方法将在视图控制器的 init 或 viewWillAppear 上调用。我正在重用 animationBlock 变量来注册不同的动画。

最后,当一个控制器被弹出时,它会调用以下代码作为其释放序列的一部分:

- (void)cleanupAnimations
{
  [[AnimationFactory sharedFactory] removeAnimationBlockForKey:@"SeqFadeInAnimation"];
  [[AnimationFactory sharedFactory] removeAnimationBlockForKey:@"StuffUpAnimation"];
}

根据 Instruments,我每次都会泄漏:

[[AnimationFactory sharedFactory] registerAnimationBlock:animationBlock forKey:@"StuffUpAnimation"];

从第一个代码 sn-p 转换为:

[self.animationBlocks setObject:[animationBlock copy] forKey:key];
[animationBlock release];

据我所知:

  1. 我正在视图控制器中创建堆栈块
  2. 然后将其传递给单例,该单例创建它的堆副本,将其存储在可变字典中并调用 release 以平衡块上增加的保留计数。
  3. 一旦声明它的视图控制器的方法超出范围,原始堆栈块应该不再存在。
  4. 我什至没有从块的封闭范围中引用任何东西,所以它不应该保留自我或任何变量/对象。它在执行时从参数中获取要使用的东西。

所以我不知道泄漏在哪里。此外,当视图控制器被弹出时,我会从 animationBlocks 数组中删除块,但我什至不需要这个(除了在我完成后立即回收内存)。因为下次用户推动同一个视图控制器时,它将使用相同的键重新注册动画块,并且使用现有键调用 setObject:withKey 将导致释放被发送到散列到该键的对象然后将新对象设置到该位置。

我忽略了什么?

【问题讨论】:

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


    【解决方案1】:

    您应该发布您制作的副本,而不是原始块。你需要做的:

    animationBlock = [animationBlock copy];
    [self.animationBlocks setObject:animationBlock forKey:key];
    [animationBlock release];
    

    【讨论】:

    • 实际上,我注意到泄漏仅发生在我引用 self.view 或类似的某些动画块中,从而导致保留周期。但是我现在将尝试使用您的代码,因为这可能会破坏保留周期
    • 好的,保留周期是个问题。我想我假设动画在完成后自然会过期,并且您将清除已完成动画的 animationsBlock 数组。
    • 不,例如,可以根据返回值重放或删除块。但是,您的答案仍然是正确的:因为我在参数上调用 release 而不是堆副本,所以即使从字典中删除块,我也会泄漏。
    猜你喜欢
    • 2018-05-25
    • 2011-03-13
    • 1970-01-01
    • 2014-06-30
    • 2011-03-23
    • 1970-01-01
    • 2012-01-23
    • 2021-08-29
    • 1970-01-01
    相关资源
    最近更新 更多