【问题标题】:Retain cycle with CCAction and CCCallFunc/CCCallBlock使用 CCAction 和 CCCallFunc/CCCallBlock 保持循环
【发布时间】:2012-10-13 14:07:05
【问题描述】:

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

谢谢!

【问题讨论】:

    标签: cocos2d-iphone automatic-ref-counting retain


    【解决方案1】:

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

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

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

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

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

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

    • 任何节点都应该只保留作为其子或孙之一的另一个节点

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

    【讨论】:

    • 非常感谢。我已经得出了相同的结论并重写了代码以完全不保留操作,并且看不到性能下降(不是我真正期望的)。我不知道 -(void)cleanup 方法 - 不知道我是怎么错过的。但是我确实尝试在 dealloc 中将 ivar 归零,正如你所说的那样 - 到那时为时已晚。而且我已经切换到块 - 但我也没有意识到 CCCallFunc 对 ARC 不安全 - 我只是认为块更干净。
    • 实际上你根本无法在 Xcode 4.5 上开发第一代和第二代设备(armv6 设备)。最低目标版本为 4.3。
    • @newacct - 是的,几天后我去购买二手的第二代 Touch 进​​行测试时发现了这一点。但看起来你可以定位 4.3,它仍然不支持弱引用 - 除非我弄错了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-25
    • 1970-01-01
    相关资源
    最近更新 更多