私は、ナビゲーションコントローラーとの間でプッシュおよびポップされるさまざまなビューコントローラーが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"];
上記のメソッドは、たとえばViewControllerのinitまたはviewWillAppearで呼び出されます。そして、さまざまなアニメーションを登録するために、animationBlock変数を再利用しています。
最後に、コントローラーがポップされると、deallocシーケンスの一部として以下を呼び出します。
- (void)cleanupAnimations
{
[[AnimationFactory sharedFactory] removeAnimationBlockForKey:@"SeqFadeInAnimation"];
[[AnimationFactory sharedFactory] removeAnimationBlockForKey:@"StuffUpAnimation"];
}
Instrumentsによると、私は毎回リークします。
[[AnimationFactory sharedFactory] registerAnimationBlock:animationBlock forKey:@"StuffUpAnimation"];
これは、最初のコードスニペットから、次のように変換されます。
[self.animationBlocks setObject:[animationBlock copy] forKey:key];
[animationBlock release];
私が理解する限りでは:
- ビューコントローラでスタックブロックを作成しています
- 次に、それをシングルトンに渡します。シングルトンは、そのヒープコピーを作成し、それを可変ディクショナリに格納し、releaseを呼び出して、ブロックの保持カウントの増加のバランスを取ります。
- 元のスタックブロックは、それが宣言されたView Controllerのメソッドがスコープ外になると、存在しなくなるはずです。
- 私はブロックの囲んでいるスコープから何も参照していないので、自分自身や変数/オブジェクトを保持するべきではありません。実行時にパラメータから処理するものを取得します。
だから私はリークがどこにあるのかわかりません。さらに、View Controllerがポップされたときに、animationBlocks配列からブロックを削除していますが、これは必要ありません(使い終わったらすぐにメモリを再利用する以外)。次回ユーザーが同じViewControllerをプッシュするようにすると、アニメーションブロックが同じキーで再登録され、既存のキーでsetObject:withKeyを呼び出すと、そのキーにハッシュされたオブジェクトにリリースが送信されます。次に、新しいオブジェクトをその場所に設定します。
私は何を見落としていますか?