web-dev-qa-db-fra.com

iOS 7 Sprite Kit libérant de la mémoire

Je construis un jeu iOS destiné au nouvel iOS 7 et au kit Sprite, en utilisant des nœuds d'émetteurs et la physique pour améliorer le jeu. Lors du développement de l'application, j'ai rencontré un grave problème: vous créez vos scènes, nœuds, effets, mais lorsque vous avez terminé et devez retourner à l'écran principal, comment libérer toute la mémoire allouée par ces ressources?

Idéalement, ARC devrait tout libérer et l'application devrait revenir au niveau de consommation de mémoire qu'elle avait avant de créer la scène, mais ce n'est pas ce qui se produit.

J'ai ajouté le code suivant, en tant que méthode dealloc de la vue, qui dessine la scène et est chargé de tout supprimer à la fermeture (supprimé):

- (void) dealloc
{
    if (scene != nil)
    {
        [scene setPaused:YES];

        [scene removeAllActions];
        [scene removeAllChildren];

        scene = nil;

        [((SKView *)sceneView) presentScene:nil];

        sceneView = nil;
    }
}
  • sceneView est un UIView, qui est le conteneur de la scène
  • scene est une extension de la classe SKScene, créant tous les objets SKSpriteNode

J'apprécierais beaucoup toute aide sur ce sujet.

19
Lehel Medves

J'ai eu beaucoup de problèmes de mémoire avec Sprite Kit et j'ai utilisé un ticket d'assistance technique pour obtenir des informations, ce qui peut se rapporter ici. Je demandais si commencer une nouvelle SKScene libérerait totalement toute la mémoire utilisée par la précédente. J'ai découvert ceci:

La mémoire sous-jacente allouée par + textureWithImageNamed: peut ou non (généralement pas) être libérée lors du passage à un nouveau SKScene. Vous ne pouvez pas compter sur cela. iOS libère la mémoire mise en cache par + textureWithImageNamed: ou + imageNamed: quand bon lui semble, par exemple lorsqu'il détecte une condition de mémoire insuffisante. 

Si vous souhaitez que la mémoire soit libérée dès que vous en avez terminé avec les textures, vous devez éviter d'utiliser + textureWithImageNamed:/+ imageNamed:. Une alternative pour créer SKTextures consiste à: créer tout d'abord UIImages avec + imageWithContentsOfFile :, puis créer SKTextures à partir des objets UIImage obtenus en appelant SKTexture/+ textureWithImage: (UIImage *).

Je ne sais pas si cela aide ici.

18
Cocorico

Tout ce code est superflu. À condition que vous n'ayez aucune fuite de mémoire ou que vous conserviez des cycles dans votre code, une fois que vous avez libéré la vue Sprite Kit, tout sera effacé de la mémoire.

Sous le capot, Sprite Kit utilise un mécanisme de mise en cache, mais nous n’avons aucun moyen de le contrôler. Nous n’aurions pas besoin de le faire s’il est correctement implémenté (ce qui est sûr à supposer).

Si ce n'est pas ce que vous voyez dans Instruments, vérifiez les cycles de rétention, les fuites. Vérifiez que dealloc de la scène et de la vue est appelé. Assurez-vous qu'il ne reste aucune référence forte à la vue, à la scène ou à d'autres nœuds dans d'autres objets (singletons et variables globales en particulier).

10
LearnCocos2D

Après avoir combattu pendant quelques jours, la clé était en fait: [SceneView presentScene: nil]; Ou pour Swift: SceneView.presentScene (nil)

ce qui peut être fait dans viewDidDisappear. Sans cela, votre vision restera accrochée à la scène, même après avoir été congédiée, et continuera à vous mordre la mémoire.

8
madwhistler

Swift 3 :

Dans mon expérience personnelle, j'ai résolu avec l'aide des instruments Xcode, tout d'abord avec le moniteur d'activité qui m'a immédiatement montré l'énorme augmentation de mémoire, qu'avec l'allocation et les fuites.

Mais il existe aussi un moyen utile, la console de débogage avec 

deinit {
       print("\n THE SCENE \(type(of:self)) WAS REMOVED FROM MEMORY (DEINIT) \n")
}

C'est une autre aide pour voir si deinit a été appelé à chaque fois que vous souhaitez supprimer une scène.

Vous n'avez jamais de références fortes à la scene ou parent dans vos classes, si vous avez quelqu'un, vous devez le transformer en faible comme par exemple:

weak var parentScene:SKScene?

Même chose pour le protocole, vous pouvez le déclarer faible, comme dans cet exemple, en utilisant la propriété class:

protocol ResumeBtnSelectorDelegate: class {
    func didPressResumeBtn(resumeBtn:SKSpriteNode)
}

weak var resumeBtnDelegate:ResumeBtnSelectorDelegate?

ARC fait tout le travail dont nous avions besoin mais, si vous pensez avoir oublié d'écrire correctement certaines propriétés (initiliazation, blocs ..), j'ai également utilisé des fonctions comme celle-ci pour mes phases de débogage :

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()
}
4
Alessandro Ornano

J'ai eu un problème similaire à vous @ utilisateur2857148. Je présenterais un VC avec:

[self presentViewController:myViewController animated:YES completion:nil];

Dans le @implementation myViewController j'avais:

- (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];
} 

Le problème était dans:

self.ballonMGScene.parentVC = self;

depuis dans:

@interface MBDBallonMiniGame : SKScene <SKPhysicsContactDelegate>

parentVC a été déclaré avec un fort: 

@property (nonatomic,strong) WBMMiniGameVCTemplate *parentVC;

Solution 1:

et le changer pour:

@property (nonatomic,weak) WBMMiniGameVCTemplate *parentVC;

résolu le problème pour moi.

Explication: La référence au parentVC (myViewController) qui était un UIViewController a été stockée quelque part. Étant donné que ce VC avait une forte référence au SKScene, il était stocké avec ce dernier. J'ai même eu la sortie de console de ce SKScene comme s'il était toujours actif. Mon meilleur argument sur la raison pour laquelle cela m’est arrivé est que j’ai les indications les plus solides. 

Solution 2:

Dans ma myViewController sous: 

- (void)viewDidDisappear:(BOOL)animated

J'ai appelé :

self.ballonMGScene.parentVC = nil;

En quittant le VCactuel _ (myViewController), je règle le pointeur sur nil, en supprimant la mémoire et tout le reste.

Ces 2 solutions ont fonctionné pour moi. Je l'ai testé avec le débogueur. La consommation de mémoire a augmenté et diminué correctement.

J'espère que cela aide à comprendre le problème et les solutions.

1
MB_iOSDeveloper

Essayez de conserver la scène avant de supprimer la scène.

-(void)dealloc
{
[sceneView presentScene:nil];
[sceneView release];
[super dealloc];
}
0
parameciostudio

Cela a pris quelques étapes, mais j'ai complètement résolu mon problème:

1) Dans My ViewControl, j'ai créé une méthode pour forcer la destruction de tous les enfants:

-(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) Puisque j'avais créé un protocole puissant dans ma scène et que je l'avais référencé dans mon ViewControl et que ma scène était forte également, j'ai détruit toutes les références suivantes:

[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;
}