【问题标题】:CCNode retained by CCCallBlock after stopAllActions is called调用 stopAllActions 后 CCCallBlock 保留的 CCNode
【发布时间】:2013-03-16 22:26:27
【问题描述】:

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

该函数工作得非常好,但如果我希望 CCNode 持续跟随路径,我通过传入的 Block 执行该路径,最终调用相同的函数(有点递归,但不是真的)。​​

我发现的问题是,由于该函数在内部使用块,因此即使在调用 stopAllActions 或 removeFromParentAndCleanup:YES 之后,正在运行操作的 CCNode 也会保留,即使 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 文档,我们有:

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

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

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

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

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

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

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

有什么建议吗?

【问题讨论】:

    标签: cocos2d-iphone memory-leaks retain ccnode


    【解决方案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]]];
    }
    

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

    【讨论】:

    • 是的,谢谢。其他代码大约有 12 个月的历史。第二次改进的方式总是令人惊奇。
    • 或者第三个,第四个……;)
    【解决方案2】:

    这段代码很复杂。不是真正的“帮手”。

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

    我还看到了一些真正令人讨厌的问题的可能性,其中之一可能是保留的原因。如果不大幅重构此代码,我看不出如何修复它。

    一个问题是操作数。它在(复制的)块内使用,该块将保留操作数。但更糟糕的是,操作数本身使用 moveToNextPoint 块运行一个序列,这与您当时正在(重新)分配的相同。我不知道那是合法的。此外,调用块操作也会复制块,使事情变得更加复杂,因为这也可能保留操作数。

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

    您还传递了一个处理程序块,该处理程序块用于将被复制的 CCCallBlock 操作块,这意味着处理程序块也至少在序列期间保留。

    长话短说,这段代码违反了一些内存管理准则,并且使用了几个交错的块使其难以调试。仅此方法的参数列表就是一种代码异味:它同时做了太多不同的事情。

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

    【讨论】:

    • 谢谢。是的,虽然效果很好,但看起来确实很复杂。
    • 我将首先将其重写为新的 CCActionInterval。 moveToNextPoint 块是必要的,以保持节点从一个 10 度点移动到下一个点。我认为如果我将其更改为一个动作,我可以消除逐步移动节点的需要。我不同意参数的数量。拥有一种通过圆弧/圆移动对象的方法非常有用,并且能够使用开始/结束角度和开始/结束半径来定制动作会给你一个非常强大和灵活的动作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-14
    • 2019-06-04
    相关资源
    最近更新 更多