我正处于发布第一款游戏的最后阶段,在运行 Instruments:Leaks & Allocations 后,我发现我的代码中存在由保留周期引起的泄漏。我正在使用 Cocos2d 2.0,并使用 ARC 编译我的应用程序,我应该提到的是,我在 ARC 之前启动了该项目,并使用 Xcode 重构工具对其进行转换。我的游戏每个屏幕有几个动画对象,每个对象都有少量(1-7)个该对象的动画“变体”(即谷仓打开一次显示一匹马,另一次显示斑马)。我有一个代表每个动画的类,还有一个代表每个变体的类。该变体从一系列帧创建一个 CCAnimation,然后创建一个 Action ,每当在正确区域接收到触摸事件时就会运行该 Action 。此操作是导致我的保留周期的原因。我对 ivar 操作的声明如下所示:
@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 的顺序因参数而异,但通常看起来像这样:
[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,并传入一个它想要在操作完成时执行的 block 。当使用类想要播放动画变体时,它会这样做:
[self runAction: variant.action];
我尝试将 _action 声明为:
CCAction* __unsafe_unretained _action;
这当然破坏了保留周期,但是操作被破坏了,并且在需要时不再存在(这正是您所期望的,因为 __unsafe_unretained 不保留)。我知道 __weak 是推荐的解决方案,但由于我的目标是 iOS 4 及更高版本,我认为它不适合我。
我的代码中有另一个保留周期,与这个完全一样,也是由保留(当然使用 ARC 自动)包含 CCCallFunc/CCCallBlock 的 CCSequence 引起的。我通过在需要时重新创建它来解决这个问题,在这种情况下我也可以这样做,但这些动画在整个游戏中可能会触发几百次,所以我希望遵循推荐的 Cocos2d 最佳实践并保留操作。
谢谢!
最佳答案
保留操作并不是最佳实践。这甚至不是一个好的做法。尽管它受到许多人的大力推荐,但不幸的是。
保留操作在许多情况下有效,但在其他情况下会失败,导致对象泄漏。我猜你的情况可能就是其中之一。
由于您的目标是 iOS 4,因此无法使用弱引用。但您可能应该重新考虑,除非您必须针对剩余的少数第一代和第二代设备。否则,谷歌搜索 iOS 5 的采用率。少数尚未更新的设备远低于合理阈值,特别是如果您认为这些用户可能不再购买(很多)应用程序。
既然您指的是CCCallFunc,请确保不使用它们并替换为CCCallBlock。 CCCallFunc 与 ARC 一起使用并不安全,特别是当您必须将数据对象 __bridge_transfer 转换为 void* 时(也是不好的做法)。
总是有可能永远不会发生必要的桥接转换回原始对象,然后 ARC 就没有机会清理该对象。使用 CCCallFunc,当您运行 call func 操作但该操作在调用回调选择器之前停止时,可能会发生这种情况,例如通过更改场景或停止操作/序列。
如果不遵循这条规则,Cocos2D 也容易出现循环引用:
- 任何节点都应该只保留作为其子节点或孙子节点之一的另一个节点
在所有其他情况下(即节点保留(祖父)父节点或兄弟节点),您必须确保在 -(void) 清理方法中将这些引用置零。在 -(void) dealloc 中这样做已经太晚了,因为当存在保留周期时,对象将永远不会到达 dealloc。
关于cocos2d-iphone - 使用 CCAction 和 CCCallFunc/CCCallBlock 保留循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12873866/