iOS 7 Sprite Kit liberando memoria
memory-management physics (7)
Estoy construyendo un juego iOS para el nuevo iOS 7 y Sprite Kit, usando nodos de emisor y física para mejorar el juego. Al desarrollar la aplicación, me encontré con un problema grave: creaste tus escenas, nodos, efectos, pero cuando terminas y necesitas regresar a la pantalla principal, ¿cómo liberas toda la memoria asignada por estos recursos?
Idealmente, ARC debería liberar todo y la aplicación debería volver al nivel de consumo de memoria que tenía antes de crear la escena, pero esto no es lo que sucede.
He agregado el siguiente código, como el método dealloc de la vista, que dibuja la escena y es responsable de eliminar todo al cerrar (eliminar):
- (void) dealloc
{
if (scene != nil)
{
[scene setPaused:YES];
[scene removeAllActions];
[scene removeAllChildren];
scene = nil;
[((SKView *)sceneView) presentScene:nil];
sceneView = nil;
}
}
- sceneView es un UIView, que es el contenedor de la escena
- escena es una extensión de la clase SKScene, creando todos los objetos SKSpriteNode
Apreciaría mucho cualquier ayuda en este asunto.
Todo ese código es superfluo. Siempre que no tenga pérdidas de memoria o retenga ciclos en su código, una vez que suelte la vista del Kit Sprite todo se borrará de la memoria.
Bajo el capó Sprite Kit emplea un mecanismo de caché, pero no tenemos forma de controlarlo, ni deberíamos hacerlo si se implementa correctamente (lo cual es seguro asumir).
Si esto no es lo que está viendo en los instrumentos, verifique si conserva los ciclos, las fugas. Verifique que se descuelgue el desglose de la escena y la vista. Asegúrese de que no permanezcan referencias fuertes a la vista, a la escena u otros nodos en otros objetos (especialmente en singletons y variables globales).
Tenía muchos problemas de memoria con Sprite Kit, y utilicé un ticket de soporte técnico para obtener información, y puede relacionarse aquí. Estaba preguntando si comenzar un nuevo SKScene liberaría totalmente toda la memoria que utilizó el anterior. Descubrí esto:
La memoria subyacente asignada por + textureWithImageNamed: puede o no (por lo general, no) liberarse al cambiar a un nuevo SKScene. No puedes confiar en eso. iOS libera la memoria en caché mediante + textureWithImageNamed: o + imageNamed: cuando lo considera apropiado, por ejemplo, cuando detecta una condición de poca memoria.
Si desea liberar la memoria tan pronto como haya terminado con las texturas, debe evitar el uso de + textureWithImageNamed: / + imageNamed :. Una alternativa para crear SKTextures es: primero crear UIImages con + imageWithContentsOfFile :, y luego crear SKTextures a partir de los objetos UIImage resultantes llamando SKTexture / + textureWithImage: (UIImage *).
No sé si esto ayuda aquí.
Tuve un problema similar al tuyo @ user2857148. Presentaría un VC con:
[self presentViewController:myViewController animated:YES completion:nil];
En @implementation myViewController
tuve:
- (void)viewDidLayoutSubviews
{
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
self.ballonMGScene = [[MBDBallonMiniGame alloc] initWithSize:skView.bounds.size andBallonImageNames:self.ballonObjectsArray];
self.ballonMGScene.parentVC = self;
self.ballonMGScene.scaleMode = SKSceneScaleModeAspectFill;
self.ballonMGScene.physicsWorld.gravity = CGVectorMake(0, 0);
// Present the scene.
[skView presentScene:self.ballonMGScene];
}
El problema fue en:
self.ballonMGScene.parentVC = self;
ya que en:
@interface MBDBallonMiniGame : SKScene <SKPhysicsContactDelegate>
parentVC fue declarado fuerte:
@property (nonatomic,strong) WBMMiniGameVCTemplate *parentVC;
Solución 1:
y cambiándolo a:
@property (nonatomic,weak) WBMMiniGameVCTemplate *parentVC;
resuelto el problema para mí
Explicación: La referencia a parentVC ( myViewController
) que era un UIViewController
se ha almacenado en alguna parte. Como este VC tenía una fuerte referencia al SKScene, se almacenó con él. Incluso tuve salida de consola de este SKScene como si aún estuviera activo. Mis mejores preguntas sobre por qué me sucedió esto es que tengo los mejores consejos.
Solución 2:
En mi myViewController
en:
- (void)viewDidDisappear:(BOOL)animated
Llame :
self.ballonMGScene.parentVC = nil;
Al salir del VC actual ( myViewController
) myViewController
el puntero a nil, quitando la memoria y todo junto con ella.
Estas 2 soluciones funcionaron para mí. Lo probé con el depurador. El consumo de memoria subió y bajó correctamente.
Espero que esto ayude a entender el problema y las soluciones.
Intenta retener el sceneView antes de eliminar la escena.
-(void)dealloc
{
[sceneView presentScene:nil];
[sceneView release];
[super dealloc];
}
Después de luchar contra esto por un par de días, la clave era de hecho: [sceneView presentScene: nil]; O para Swift: sceneView.presentScene (nil)
que se puede hacer en viewDidDisappear. Sin esto, su punto de vista se mantendrá en la escena para salvar la vida, incluso después de ser despedido, y continuará masticando la memoria.
Dio algunos pasos, pero resolví completamente mi problema:
1) En My ViewControl, creé un método para forzar la destrucción de todos los niños:
-(void)destroyAllSub:(SKNode*)node
{
if(node == nil) return;
if(![node isKindOfClass:[SKNode class]]) return;
[node removeAllActions];
for (SKNode *subNode in node.children) {
[self destroyAllSub:subNode];
}
[node removeAllChildren];
}
2) Como creé un protocolo fuerte en mi escena y lo mencioné en mi ViewControl y mi escena también fue fuerte, destruí todas las referencias de la siguiente manera:
[self.mainScene.view presentScene:nil]; //mainScene: the name of the Scene pointer
self.mainScene.myProt = nil; //myProt: The name of the strong protocol
@autoreleasepool {
[self destroyAllSub:self.mainScene];
self.mainScene = nil;
}
Swift 3 :
En mi experiencia personal lo he resuelto con la ayuda de los instrumentos de Xcode, en primer lugar con el monitor de actividad que me mostró inmediatamente el enorme aumento de memoria, que con la asignación y las fugas.
Pero también hay una forma útil, la consola de depuración con
deinit {
print("/n THE SCENE /(type(of:self)) WAS REMOVED FROM MEMORY (DEINIT) /n")
}
Esta es otra ayuda para ver si se deinit
cada vez que desea eliminar una escena.
Nunca tienes referencias fuertes a la scene
o al parent
en tus clases, si tienes a alguien debes transformarlo en débil como por ejemplo:
weak var parentScene:SKScene?
Lo mismo para el protocolo, puede declararlo débil como este ejemplo usando la class
propiedad:
protocol ResumeBtnSelectorDelegate: class {
func didPressResumeBtn(resumeBtn:SKSpriteNode)
}
weak var resumeBtnDelegate:ResumeBtnSelectorDelegate?
ARC hace todo el trabajo que necesitamos pero, si crees que has olvidado escribir correctamente alguna propiedad (initiliazation, blocks ...) también he usado algunas funciones como esta para mis fases de depuración :
func cleanScene() {
if let s = self.view?.scene {
NotificationCenter.default.removeObserver(self)
self.children
.forEach {
$0.removeAllActions()
$0.removeAllChildren()
$0.removeFromParent()
}
s.removeAllActions()
s.removeAllChildren()
s.removeFromParent()
}
}
override func willMove(from view: SKView) {
cleanScene()
self.removeAllActions()
self.removeAllChildren()
}