Présentation du journal d'allocation de memory d'Instruments sur iOS

J'ai construit une application iOS qui est presque terminée, cependant, j'ai récemment connu qu'il se bloque après tout en raison de la "pression de la memory". J'ai donc commencé à profiler les allocations de memory dans Instruments et bien sûr, l'application utilise beaucoup de memory et elle semble seulement augmenter pendant l'utilisation.

Cependant, relativement nouveau à l'allocation de memory d'Instruments je ne suis pas tout à fait capable de déchiffrer où 52% des allocations sont faites, comme vu dans la capture d'écran ci-dessous:

entrez la description de l'image ici

Cela a évidemment quelque chose à voir avec Core Animation, mais ce qui est difficile à déterminer, alors j'ai pensé que certains esprits intelligents pourraient connaître la réponse à cette question.

Miette de pain:

Mon application utilise des interruptions personnalisées, lorsque vous passez d'un controller de vue à un autre, où il y a beaucoup d'animation. Voici un exemple:

@interface AreaToKeyFiguresSegue : UIStoryboardSegue @end ... @implementation AreaToKeyFiguresSegue - (void)perform { [self sourceControllerOut]; } - (void)sourceControllerOut { AreaChooserViewController *sourceViewController = (AreaChooserViewController *) [self sourceViewController]; KeyFigureViewController *destinationController = (KeyFigureViewController *) [self destinationViewController]; double ratio = 22.0/sourceViewController.titleLabel.font.pointSize; sourceViewController.titleLabel.adjustsFontSizeToFitWidth = YES; [UIView animateWithDuration:TRANSITION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ // Animate areaChooser sourceViewController.areaChooserScrollView.alpha = 0; sourceViewController.areaScrollViewVerticalSpaceConstraint.constant = -300; sourceViewController.backButtonVerticalConstraint.constant = 20; sourceViewController.backButton.transform = CGAffineTransformScale(sourceViewController.backButton.transform, ratio, ratio); sourceViewController.backButton.titleLabel.textColor = [UIColor redKombitColor]; sourceViewController.backArrowPlaceholderVerticalConstraint.constant = 14; sourceViewController.backArrowPlaceholder.alpha = 1; sourceViewController.areaLabelVerticalConstraint.constant = 50; sourceViewController.areaLabel.alpha = 1; [sourceViewController.view layoutIfNeeded]; } completion:^(BOOL finished) { [destinationController view]; // Make sure destionation view is initialized before animating it [sourceViewController.navigationController pushViewController:destinationController animated:NO]; // Push new viewController without animating it [self destinationControllerIn]; // Now animate destination controller }]; } - (void)destinationControllerIn { AreaChooserViewController *sourceViewController = (AreaChooserViewController *) [self sourceViewController]; KeyFigureViewController *destinationController = (KeyFigureViewController *) [self destinationViewController]; destinationController.keyFigureTableViewVerticalConstraint.constant = 600; destinationController.keyFigureTableView.alpha = 0.0; destinationController.allFavoritesSegmentedControl.alpha = 0.0; [destinationController.view layoutIfNeeded]; [sourceViewController.segueProgress setHidden:YES]; } @end 

Et chaque fois qu'un controller de vue doit apparaître, je fais simplement l'inverse:

 - (IBAction)goBack:(id)sender { [UIView animateWithDuration:TRANSITION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ [self.keyFigureTableView setAlpha:0]; self.keyFigureTableViewVerticalConstraint.constant = 700; [self.allFavoritesSegmentedControl setAlpha:0]; [self.view layoutIfNeeded]; } completion:^(BOOL finished) { [self.navigationController popViewControllerAnimated:NO]; // Pop viewController without animating it }]; } 

Modifier:

La plus grande partie de l'allocation de memory a lieu lorsque vous appuyez sur un controller de vue, même s'il a déjà été affiché auparavant. Ie allant de

A -> B -> C

B <- C

B -> C

où "->" = push et "<-" = pop, chaque "->" alloue plus de memory et "<-" n'en libère jamais.

Plus de détails

Je n'ai pas de zombies et pas de fuites selon les instruments. L'parsing statique ne donne rien non plus. Mon application ne cesse d'allouer de la memory jusqu'à ce qu'elle se bloque finalement.

Environ 70% de mon allocation de memory se produit dans la stack d'appels suivante, ce qui n'a rien à voir avec mon code (arborescence d'appels inversés):

entrez la description de l'image ici

Voici comment je débogue ces derniers.

  1. Dans Instruments, utilisez l'instrument Allocations et activez "Record reference counts"

entrez la description de l'image ici

  1. Exécutez votre application à un état stable, y compris effectuer l'opération que vous pensez fuit quelques fois.

  2. Dans Instruments, définissez votre niveau de memory de base à l'aide des marqueurs d'input / sortie.

  3. Effectuez l'opération qui, selon vous, fuit plusieurs fois. (disons 7)

  4. Dans Instruments, passez à la vue qui affiche toutes les allocations et searchz les objects qui ont été alloués mais qui n'ont pas été libérés le même nombre de fois que l'opération que vous venez d'effectuer (peut-être encore 7 fois). Vous voudrez d'abord essayer de find un object spécifique à votre programme … Préférez donc MyNetworkOperation instances de MyNetworkOperation place des classs generics de base comme NSData . cliquez sur la flèche à côté de la classe qui vous intéressecliquez sur la flèche en regard d'un objet affecté

  5. Sélectionnez l'un des objects qui n'a pas été désalloué et examinez son historique d'allocation. Vous pourrez voir la stack d'appels pour chaque alloc / retain / release / autorelease pour l'object en question. L'un des appels vous semblera probablement suspect. Historique de la conservation / libération de l'objet sélectionné

Je suppose que ces étapes s'appliquent plus dans un environnement non-ARC. Sous ARC, vous searchz peut-être un cycle de conservation.

En général, vous pouvez éviter les cycles de conservation en vous assurant que vos references fortes ne vont que dans une direction … Par exemple, une vue a de fortes references à ses sous-vues et chaque sous-vue ne doit utiliser que des references faibles. Ou peut-être votre controller de vue a une forte reference à votre sharepoint vue. Votre vue doit seulement avoir une reference faible à son controller de vue. Une autre façon de dire ceci est de décider dans chaque relation quel object "possède" l'autre.

Instruments dit que vos UIViews fuient (ou ne sont pas libérés pour être précis).

Chaque poussée créera un nouveau controleur de destinationController et la vue destinationController's sera sauvegardée par un CALayer et CALayer supprimera la memory.

Pour le prouver, vous pouvez simplement implémenter dealloc pour votre destinationController et y définir un point d'arrêt pour voir s'il est appelé.

Si le dealloc est appelé, vous pouvez le faire pour d'autres objects afin de savoir lequel. (Comme destinationController.view )

Pourquoi une fuite?

1, vous pouvez avoir des cycles de retenue qui capturent destinationController . Il est difficile de déboguer, vous devrez peut-être vérifier tous vos codes liés.

2, chaque destinationController peut être retenu par un object long vivant. (NSTimer répété qui ne sont pas invalidés, Singleton, RootViewController, CADisplayLink …)

3, faux cache. Vous cachez quelque chose et essayez de le réutiliser. Cependant, la logique de cache a des bogues et ces objects ne sont jamais réutilisés et de nouveaux objects sont constamment insérés.

Votre animation contient principalement des valeurs alpha ou des colors changeantes. Pour réduire le code d'animation en mode personnalisé, je vous suggère de déplacer votre code d'animation pour le controller de vue de destination (c'est-à-dire la méthode destinationControllerIn ) vers viewDidLoad: du controller de vue de destination.

Vérifiez d'abord si (les diagnostics du système) vous avez activé les zombies. Zombies signifie que rien n'est jamais supprimé. Étant donné que votre carte memory ne tombe jamais du tout ce serait mon premier chèque.