调用stopAllActions后,CCCallBlock保留CCNode

时间:2013-03-16 22:26:28

标签: cocos2d-iphone memory-leaks retain ccnode

我有一个实用程序函数,用于在圆形路径中移动CCNode,无论是整圆还是部分圆。

该函数工作得很好,但是如果我希望CCNode继续遵循路径,我通过传入的块来执行,最终调用相同的函数(有点递归,但不是真的)。​​

我发现的问题是因为函数在内部使用了块,正在运行动作的CCNode被保留,甚至在调用stopAllActions或removeFromParentAndCleanup:YES之后,即使清除了CCNode,从屏幕上删除后,它会保留在内存中并且不会被释放。这似乎会影响性能,即使节点没有显示为CCNode,其他依赖者仍然(某种程度上)在cocos2d系统中。

这是移动CCNode的功能:

@interface CocosUtil : NSObject {

}

typedef void (^NodeCompletionBlock)(CCNode *sprite);

+ (void) moveOperand:(CCNode*)operand throughCircleWithCentre:(CGPoint)centreOfElipse
     startingDegrees:(float)startingDegrees
     endingAtDegrees:(float)endingDegrees
      startingRadius:(float)startingRadius
        endingRadius:(float)endingRadius
 withInitialDuration:(ccTime)initialDuration
    withMainDuration:(ccTime)duration
           clockwise:(BOOL)clockwise
     completionBlock:(NodeCompletionBlock)handler;

@end


@implementation CocosUtil

+ (float) angleFromDegrees:(float)deg {
    return fmodf((450.0 - deg), 360.0);
}

// Calculates the angle from one point to another, in radians.
//
+ (float) angleFromPoint:(CGPoint)from toPoint:(CGPoint)to {
    CGPoint pnormal = ccpSub(to, from);
    float radians = atan2f(pnormal.x, pnormal.y);

    return radians;
}

+ (CGPoint) pointOnCircleWithCentre:(CGPoint)centerPt andRadius:(float)radius atDegrees:(float)degrees {
    float x = radius + cos (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    float y = radius + sin (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    return ccpAdd(centerPt, ccpSub(CGPointMake(x, y), CGPointMake(radius, radius)));
}

+ (void) moveOperand:(CCNode*)operand throughCircleWithCentre:(CGPoint)centreOfElipse
     startingDegrees:(float)startingDegrees
     endingAtDegrees:(float)endingDegrees
      startingRadius:(float)startingRadius
        endingRadius:(float)endingRadius
 withInitialDuration:(ccTime)initialDuration
    withMainDuration:(ccTime)duration
           clockwise:(BOOL)clockwise
     completionBlock:(NodeCompletionBlock)handler {
    float range;

    if (clockwise == YES) {
        if (endingDegrees <= startingDegrees) {
            range = (360.0 + endingDegrees) - startingDegrees;
        } else {
            range = endingDegrees - startingDegrees;
        }
    } else {
        if (endingDegrees >= startingDegrees) {
            range = (360.0 + startingDegrees) - endingDegrees;
        } else {
            range = startingDegrees - endingDegrees;
        }
    }

    __block float degrees = startingDegrees;
    __block float radius = startingRadius;

    const float incrementAngle = 10.0;

    float intervals = (range / incrementAngle) - 1;

    ccTime interval = duration / intervals;
    float radiusStep = (endingRadius - startingRadius) / intervals;

    if (clockwise == YES) {
        degrees += incrementAngle;
    } else {
        degrees -= incrementAngle;
    }

    radius += radiusStep;

    __block void (^moveToNextPoint)();

    moveToNextPoint = [^(){
        if (fabsf(degrees - endingDegrees) < 1.0) {
            [operand runAction:[CCSequence actions:
                                [CCEaseBounceOut actionWithAction:
                                 [CCMoveTo actionWithDuration:interval position:[self pointOnCircleWithCentre:centreOfElipse andRadius:radius atDegrees:degrees]]],
                                [CCCallBlock actionWithBlock:
                                 ^{
                                     if (handler != nil) {
                                         handler(operand);
                                     }
                                 }],
                                nil]];
        } else {
            [operand runAction:[CCSequence actions:
                                [CCMoveTo actionWithDuration:interval position:[self pointOnCircleWithCentre:centreOfElipse andRadius:radius atDegrees:degrees]],
                                [CCCallBlock actionWithBlock:moveToNextPoint],
                                nil]];

            if (clockwise == YES) {
                degrees += incrementAngle;

                if (degrees > 360.0) {
                    degrees = degrees - 360.0;
                }
            } else {
                degrees -= incrementAngle;

                if (degrees < 0.0) {
                    degrees = degrees + 360.0;
                }
            }

            radius += radiusStep;
        }
    } copy];

    [operand runAction:[CCSequence actions:
                        [CCMoveTo actionWithDuration:initialDuration position:[self pointOnCircleWithCentre:centreOfElipse andRadius:startingRadius atDegrees:startingDegrees]],
                        [CCCallBlock actionWithBlock:moveToNextPoint],
                        nil]];

}

@end

您会注意到节点移动的弧线分为10度。这样做是为了在不编写CCActionInterval子类的情况下获得循环运动,但这意味着使用块或选择器来保持运动一直运行直到完成。

现在,为了让我的CCNode不断移动一整圈,我使用以下方法调用此函数:

- (void) moveLayer:(CCNode*)layer
startingDegrees:(float)startingDegrees
endingAtDegrees:(float)endingDegrees
startingRadius:(float)startingRadius
endingRadius:(float)endingRadius
withInitialDuration:(ccTime)initialDuration
withMainDuration:(ccTime)duration
clockwise:(BOOL)clockwise {
    [CocosUtil moveOperand:layer throughCircleWithCentre:CGPointMake(240.0, 160.0) startingDegrees:startingDegrees endingAtDegrees:endingDegrees startingRadius:startingRadius endingRadius:endingRadius withInitialDuration:initialDuration withMainDuration:duration clockwise:clockwise completionBlock:^(CCNode *sprite) {
        [self moveLayer:layer startingDegrees:startingDegrees endingAtDegrees:endingDegrees startingRadius:startingRadius endingRadius:endingRadius withInitialDuration:initialDuration withMainDuration:duration clockwise:clockwise];
    }];
}

我尝试了一些不同的东西,比如根本没有传递一个块,但是没有任何东西阻止了保留,除了根本不使用该功能。

据我所知,在XCode doco中阅读,我们有:

  

“如果在方法的实现中使用块,则对象实例变量的内存管理规则会更加微妙:

     

如果通过引用访问实例变量,则保留self;   如果按值访问实例变量,则保留变量。“

所以这告诉我,通过在我的函数中使用块我的方式导致隐藏的保留。

稍后调用stopAllActions不会触发释放。

这对我有用的唯一方法是,在我的节点的cleanup()消息中,我添加[self release]

我不喜欢这个,因为它与执行保留的代码分离。

我有一个新想法是以某种方式将其重写为新的CCActionInterval子类,但我仍然不确定这是否能解决问题。

有什么建议吗?

2 个答案:

答案 0 :(得分:1)

行。接受@ LearnCocos2D的建议,做我在这里想做的事情,是我可能想要的其他人的解决方案。

基本上,我把整个事情重写为一个相对简单的CCActionInterval子类。

根本不再涉及任何块,因此没有隐藏的保留。更清洁,更优雅(我认为)。

界面:

#import "cocos2d.h"

@interface CCMoveThroughArc : CCActionInterval

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius;

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius
                reversed:(BOOL)reversed;

/** initializes the action */
-(id) initWithDuration:(ccTime)duration
                centre:(CGPoint)centreOfCircle
     startingAtDegrees:(float)startingDegrees
       endingAtDegrees:(float)endingDegrees
      startingAtRadius:(float)startingRadius
        endingAtRadius:(float)endingRadius
              reversed:(BOOL)reversed;

@end

实施:

#import "CCMoveThroughArc.h"


@implementation CCMoveThroughArc {

    CGPoint centre_;

    float startingDegrees_;
    float endingDegrees_;
    float diffDegrees_;

    float startingRadius_;
    float endingRadius_;
    float diffRadius_;

    BOOL reversed_;
}

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius {
    return [[self alloc] initWithDuration:duration centre:centreOfCircle startingAtDegrees:startingDegrees endingAtDegrees:endingDegrees startingAtRadius:startingRadius endingAtRadius:endingRadius reversed:NO];
}

/** creates the action */
+(id) actionWithDuration:(ccTime)duration
                  centre:(CGPoint)centreOfCircle
       startingAtDegrees:(float)startingDegrees
         endingAtDegrees:(float)endingDegrees
        startingAtRadius:(float)startingRadius
          endingAtRadius:(float)endingRadius
                reversed:(BOOL)reversed {
    return [[self alloc] initWithDuration:duration centre:centreOfCircle startingAtDegrees:startingDegrees endingAtDegrees:endingDegrees startingAtRadius:startingRadius endingAtRadius:endingRadius reversed:reversed];
}

/** initializes the action */
-(id) initWithDuration:(ccTime)duration
                centre:(CGPoint)centreOfCircle
     startingAtDegrees:(float)startingDegrees
       endingAtDegrees:(float)endingDegrees
      startingAtRadius:(float)startingRadius
        endingAtRadius:(float)endingRadius
              reversed:(BOOL)reversed {
    if( (self=[super initWithDuration:duration]) ) {
        centre_ = centreOfCircle;

        startingDegrees_ = fminf(startingDegrees, endingDegrees);
        endingDegrees_ = fmaxf(startingDegrees, endingDegrees);

        startingRadius_ = startingRadius;
        endingRadius_ = endingRadius;

        reversed_ = reversed;

        diffDegrees_ = endingDegrees_ - startingDegrees_;
        diffRadius_ = endingRadius_ - startingRadius_;
    }

    return self;
}

-(id) copyWithZone: (NSZone*) zone
{
    CCAction *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] centre:centre_ startingAtDegrees:startingDegrees_ endingAtDegrees:endingDegrees_ startingAtRadius:startingRadius_ endingAtRadius:endingRadius_ reversed:reversed_];

    return copy;
}

-(CCActionInterval*) reverse
{
    return [[self class] actionWithDuration:duration_ centre:centre_ startingAtDegrees:startingDegrees_ endingAtDegrees:endingDegrees_ startingAtRadius:startingRadius_ endingAtRadius:endingRadius_ reversed:!reversed_];
}

-(void) startWithTarget:(CCNode *)aTarget
{
    NSAssert1(([aTarget isKindOfClass:[CCNode class]] == YES), @"CCMoveThroughArc requires a CCNode as target.  Got a %@", [[aTarget class] description]);

    [super startWithTarget:aTarget];
}

- (float) angleFromDegrees:(float)deg {
    return fmodf((450.0 - deg), 360.0);
}

- (CGPoint) pointOnCircleWithCentre:(CGPoint)centerPt andRadius:(float)radius atDegrees:(float)degrees {
    float x = radius + cos (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    float y = radius + sin (CC_DEGREES_TO_RADIANS([self angleFromDegrees:degrees])) * radius;
    return ccpAdd(centerPt, ccpSub(CGPointMake(x, y), CGPointMake(radius, radius)));
}

-(void) update:(ccTime) t {

    float angle;
    float radius;

    if (reversed_ == NO) {
        angle = (diffDegrees_ * t) + startingDegrees_;
        radius = (diffRadius_ * t) + startingRadius_;
    } else {
        angle = endingDegrees_ - (diffDegrees_ * t);
        radius = (diffRadius_ * (1.0 - t)) + startingRadius_;
    }

    CGPoint pos = [self pointOnCircleWithCentre:centre_ andRadius:radius atDegrees:angle];
    [(CCNode*)target_ setPosition:pos];
}

@end

使用它的代码:

- (void) moveStartingAtDegrees:(float)startingDegrees
               endingAtDegrees:(float)endingDegrees
                startingRadius:(float)startingRadius
                  endingRadius:(float)endingRadius
           withInitialDuration:(ccTime)initialDuration
              withMainDuration:(ccTime)duration {

    [self runAction:[CCRepeatForever actionWithAction:
                     [CCMoveThroughArc actionWithDuration:duration centre:CGPointZero startingAtDegrees:startingDegrees endingAtDegrees:endingDegrees startingAtRadius:startingRadius endingAtRadius:endingRadius reversed:YES]]];
}

我希望这对其他人有用。它当然帮助了我。它并没有真正回答我的实际问题,但它非常有效地处理了问题背后的问题。

答案 1 :(得分:0)

此代码令人费解。不是真正的“帮助者”。

我看到的是moveToNextPoint块的不必要副本。

我也看到了几个真正令人讨厌的问题的可能性,其中一个可能是保留的原因。我没有看到如何修复它而不会显着重构此代码。

一个问题是操作数。它在(复制)块内部使用,它将保留操作数。但更糟糕的是,操作数本身使用moveToNextPoint块运行一个序列,就像你当时(重新)分配一样。我不知道那是合法的。此外,调用块操作还将复制块,这进一步使问题复杂化,因为这也可能保留操作数。

由于此方法的上下文是不相关类的类方法,而不是操作数本身,我怀疑它也起到了作用。

您还传入一个处理程序块,该块在将被复制的CCCallBlock操作块中使用,这意味着处理程序块也至少在序列的持续时间内保留。

简而言之,此代码违反了几个内存管理指南,并且使用多个交错块会使调试变得困难。仅此方法的参数列表就是代码气味:它同时执行了太多不同的事情。

把它拆开,重新开始。考虑每个步骤需要做什么。谁拥有哪个块,哪个参考。测试保留循环/泄漏的每个步骤。问问自己:它真的需要使用这么多交错块吗?你能让它更顺序吗?谁应该运行哪些代码?也许某些部分的CCNode类别更有意义。