CCNode сохраняется в CCCallBlock после вызова stopAllActions
У меня есть служебная функция, которую я использую для перемещения 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 ответа
ХОРОШО. Принятие совета от @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]]];
}
Я надеюсь, что это будет полезно для других. Это конечно помогло мне. Это действительно не отвечает на мой фактический вопрос, но это решает проблему позади вопроса очень эффективно.
Этот код запутан. Не совсем "помощник".
То, что я вижу, является ненужной копией блока moveToNextPoint.
Я также вижу несколько возможностей для действительно неприятных вопросов, одна из которых, вероятно, является причиной сохранения. Я не вижу, как это исправить без значительного рефакторинга этого кода.
Одной из проблем является операнд. Он используется внутри (скопированного) блока, который сохранит операнд. Хуже того, сам операнд запускает последовательность, используя блок moveToNextPoint, то же самое, что вы (пере) назначаете в этот момент. Я не знал, что это было законно. Кроме того, действие callblock также будет копировать блок, что еще более усложнит ситуацию, потому что это может также сохранить операнд.
Поскольку контекст этого метода является методом класса несвязанного класса, а не самим операндом, я подозреваю, что это также играет роль.
Вы также передаете блок обработчика, который используется в блоке действия CCCallBlock, который будет скопирован, что означает, что блок обработчика также сохраняется, по крайней мере, на протяжении всей последовательности.
Короче говоря, этот код нарушает несколько рекомендаций по управлению памятью, а использование нескольких чередующихся блоков затрудняет отладку. Список параметров только для этого метода является запахом кода: он делает слишком много разных вещей одновременно.
Разбери это, начни сначала. Подумайте, где вам нужно делать то, что на каждом шагу. Кому принадлежит какой блок, какая ссылка. Проверьте каждый шаг на предмет сохранения циклов / утечек. Спросите себя: действительно ли нужно использовать такое количество чередующихся блоков? Можете ли вы сделать это более последовательным? Кто должен запускать какой код? Возможно, категория CCNode для некоторых частей имеет больше смысла.