2

我正处于发布我的第一个游戏的最后阶段,在运行 Instruments:Leaks & Allocations 之后,我发现我的代码中有一个由保留周期引起的泄漏。我正在使用 Cocos2d 2.0,并使用 ARC 编译我的应用程序,我应该提到我在 ARC 之前启动了项目,并使用 Xcode 重构工具对其进行了转换。我的游戏每个屏幕都有几个动画对象,每个对象都有少量 (1-7) 动画“变体”该对象(即谷仓打开一次显示一匹马,另一次显示一匹斑马)。我有一个代表每个动画的类,以及每个变体的另一个类。该变体从一系列帧创建一个 CCAnimation,然后创建一个动作,该动作将在正确区域收到触摸事件时运行。此操作是导致我的保留周期的原因。

@interface AnimationVariant : NSObject
{
@private
    CCAction*  _action;
...
}
@property (readonly, nonatomic) CCAction* action;
...

-(void) setupActionWithMask:(int)mask
                     cycles:(int)cycles
                   hasScale:(bool)hasScale
                      scale:(float)scale
                masterScale:(float)master_scale
                  animationFrames:(NSArray*) frames
                   duration:(float)duration
                   andBlock:(VoidBlock)block;

@end

在 setupActionWithMask 方法的实现中,我建立了一个 CCActions 的 NSMutableArray,actionList。CCActions 的顺序取决于 args,但通常看起来像这样:

[actionList addObject:[CCScaleTo actionWithDuration:0.0f scale:scale]];
[actionList addObject: [CCAnimate actionWithAnimation:animation] ];
[actionList addObject:[CCScaleTo actionWithDuration:0.0f scale:master_scale]];
[actionList addObject: [CCCallBlock actionWithBlock:block]];

我创建了这样的动作:

_action = [CCSequence actionMutableArray:actionList];

消费类创建一个 AnimationVariant 实例,设置它的属性,调用 setupActionWithMask,并传入一个它想要在动作完成时执行的块。当消费类想要播放动画变体时,它会这样做:

[self runAction: variant.action];

我尝试将 _action 声明为:

CCAction* __unsafe_unretained _action;

这当然打破了保留周期,但是该操作被破坏了,并且在需要时不再存在(这是您所期望的,因为 __unsafe_unretained 不保留)。我知道 __weak 是推荐的解决方案,但由于我的目标是 iOS 4 及更高版本,我认为它不适合我。

我的代码中有另一个保留周期,就像这个一样,也是由保留(当然是自动使用 ARC)包含 CCCallFunc/CCCallBlock 的 CCSequence 引起的。我通过在需要时重新创建它来解决这个问题,在这种情况下我也可以这样做,但是这些动画在整个游戏中可能会触发几百次,所以我希望遵循推荐的 Cocos2d 最佳实践并保留动作。

谢谢!

4

1 回答 1

2

保留操作不是最佳做法。这甚至不是一个好习惯。尽管很多人强烈推荐它,但很不幸。

保留操作在许多情况下有效,但在其他情况下失败,导致对象泄漏。我猜你的情况可能就是其中之一。

由于您的目标是 iOS 4,因此您不能使用弱引用。但您可能应该重新考虑,除非您必须针对剩余的少数第一代和第二代设备。否则,请谷歌了解 iOS 5 的采用率。少数尚未更新的设备远低于合理的阈值,特别是如果您认为这些用户可能不再购买(许多)应用程序。

由于您的意思是 CCCallFunc,请确保您不使用它们并替换为 CCCallBlock。CCCallFunc 与 ARC 一起使用并不安全,尤其是当您必须 __bridge_transfer 将数据对象强制转换为 void* 时(也是不好的做法)。

总是有可能永远不会发生必要的桥接回原始对象,然后 ARC 没有机会清理该对象。使用 CCCallFunc,当您运行调用 func 操作但该操作在调用回调选择器之前停止时可能会发生这种情况,例如通过更改场景或停止操作/序列。

如果不遵循这条规则,Cocos2D 也容易出现保留循环:

  • 任何节点都应该只保留另一个节点,它是它的子节点或孙子节点之一

在所有其他情况下(即节点保留(祖)父节点或兄弟节点),您必须确保在 -(void) 清理方法中将这些引用归零。在 -(void) dealloc 中这样做为时已晚,因为当存在保留周期时,对象永远不会到达 dealloc。

于 2012-10-13T20:37:33.997 回答