我有一个实用程序函数,用于在圆形路径中移动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子类,但我仍然不确定这是否能解决问题。
有什么建议吗?
答案 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类别更有意义。