web-dev-qa-db-fra.com

Swift Gestion de la mémoire

Cette question a été nettoyée et les informations importantes sont passées à la réponse ci-dessous.


J'ai des questions sur la gestion de la mémoire.

Je crée une application de retouche photo. Il est donc important de limiter l'utilisation de la mémoire. De plus, je ne vais pas publier de code car je n'ai pas de grosse fuite de mémoire lorsque je fais une chose spécifique. Je perds juste quelques KB/MB avec tout ce qui se passe. Et parcourir des dizaines de milliers de lignes de code pour trouver des kilo-octets n'est pas amusant;)

mon application utilise les données de base, beaucoup de choses sur le filtre, l'emplacement et les bases.

Ma première vue est juste une table qui me coûte environ 5 Mo de mémoire. Ensuite, vous prenez des photos, appliquez des filtres, cela est enregistré dans les données principales, puis vous revenez à cette première vue.

Est-il possible de vraiment se débarrasser de tout ce qui se trouve en mémoire, à l'exception des données nécessaires pour piloter cette première vue. (ce 5 Mo très économique et génial)

Ou y aura-t-il toujours quelque chose qui restera, même si vous mettez tout à zéro?


Question bonus: y a-t-il une différence de taille de fichier/charge de processeur entre UIImageJPEGRepresentation et UIImagePNGRepresentation? Je sais que vous pouvez définir une qualité de compression avec la méthode JPEG (plus difficile sur le cpu/gpu?).

J'essaie simplement de réduire la pression de la mémoire par tous les moyens possibles.


Mise à jour:

On m'a fait remarquer que la question était peut-être trop vague.

Les problèmes que j'ai rencontrés à un moment ou à un autre étaient les suivants:

  • À certains moments, l'utilisation maximale de la mémoire est trop élevée
  • La navigation vers un second Viewcontroller et inversement provoque une fuite
  • La modification d'une image provoque une fuite de mémoire.
  • L'application d'un filtre à plus de 4 à 5 images provoque un blocage en raison d'une mémoire insuffisante, il n'y a plus eu de fuite de mémoire à ce stade. (vérifié dans les instruments)

Tout cela a été testé sur un iPhone 4s, pas sur le simulateur.

Il y avait un mème ici pour alléger un peu l'ambiance sur ce site.

25
R Menke

Cette question est ouverte depuis assez longtemps et je me sens maintenant suffisamment en confiance pour y répondre.


Différents niveaux de MM:

Mémoire matérielle

Dans Swift avec [~ # ~] arc [~ # ~] nous n'avons aucun moyen de nettoyer le réel ram matériel. Nous ne pouvons que permettre à l'OS de le faire pour nous. Une partie utilise le bon code (optionals et weak) l'autre partie crée du temps pour l'OS faire son travail.

Imaginez que nous ayons une fonction qui s'exécute sur tous les threads indéfiniment. Cela fait une chose, charger une image, convertir en noir/blanc et enregistrer. Toutes les images atteignent un maximum de quelques Mo et la fonction ne crée aucune fuite de mémoire logicielle. Comme les images n'ont pas de taille définie et peuvent avoir une compression différente, elles n'ont pas la même empreinte. Cette fonction plantera toujours votre application.

Cette fuite de mémoire "matérielle" est causée par la fonction prenant toujours le prochain emplacement de mémoire disponible.

L'OS n'intervient pas pour "réellement nettoyer la mémoire" car il n'y a pas de temps d'inactivité. Mettre un délai entre chaque passe résout complètement ce problème.


MM spécifique à la langue

Casting

Certaines opérations n’ont pas d’impact sur la mémoire, d’autres le font:

let myInt : Int = 1
Float(myInt) // this creates a new instance

Essayez plutôt de lancer un casting:

(myInt as Float) // this will not create a new instance.

Types de référence vs types de valeur | Classes vs Structs

Les deux ont leurs avantages et leurs dangers.

Les structures sont gourmandes en mémoire car elles sont Types de valeur . Cela signifie qu'ils copient leurs valeurs lorsqu'ils sont affectés à une autre instance, doublant effectivement l'utilisation de la mémoire . Il n'y a aucun correctif/solution pour cela. C'est ce qui fait Structs Structs.

Les classes n'ont pas ce comportement car ce sont les types de référence . Ils ne copient pas lorsqu'ils sont attribués. Au lieu de cela, ils créent une autre référence au même objet . [~ # ~] arc [~ # ~] ou Comptage de référence automatique est ce qui garde la trace de ces références. Chaque objet a un compteur de référence. Chaque fois que vous l'attribuez, il augmente de un. Chaque fois que vous définissez une référence sur zéro, la fonction englobante se termine ou l'objet englobant se désactive, le compteur descend.

Lorsque le compteur atteint 0, l'objet est réinitialisé.

Il existe un moyen d'empêcher une instance de désinitialiser et donc de créer une fuite. Ceci s'appelle un cycle de référence fort .

Bonne explication des faibles

class MyClass {

    var otherClass : MyOtherClass?

    deinit {
        print("deinit") // never gets called
    }
}

class MyOtherClass {

    var myclass : MyClass?

    deinit {
        print("deinit") // never gets called
    }
}

var classA : MyClass? = MyClass()

// sorry about the force unwrapping, don't do it like this
classA!.otherClass = MyOtherClass()
classA!.otherClass!.myclass = classA // this looks silly but in some form this happens a lot

classA = nil
// neither the MyClass nor the MyOtherClass deinitialised and we no longer have a reference we can acces. Immortalitiy reached they have.

définissez une référence sur weak

class MyOtherClass {

    weak var myclass : MyClass?

    deinit {
        print("deinit") // gets called
    }
}

inout

Les fonctions capturent les valeurs qui leur sont transmises. Mais il est également possible de marquer ces valeurs comme inout. Cela vous permet de modifier un Struct passé à une fonction sans copier le Struct. Cela peut économiser de la mémoire, selon ce que vous réussissez et ce que vous faites dans la fonction.

C'est aussi une belle façon d'avoir plusieurs valeurs de retour sans utiliser de tuples.

var myInt : Int = 0

// return with inout
func inoutTest(inout number: Int) {

    number += 5

}

inoutTest(&myInt)
print(myInt) // prints 5

// basic function with return creates a new instance which takes up it's own memory space
func addTest(number:Int) -> Int {

    return number + 5

}

Programmation fonctionnelle

L'état est une valeur dans le temps

La programmation fonctionnelle est la contrepartie de la programmation orientée objet. La programmation fonctionnelle utilise l'état immuable.

Plus à ce sujet ici

La programmation orientée objet utilise des objets qui ont des états changeants/mutants. Au lieu de créer une nouvelle valeur, les anciennes valeurs sont mises à jour.

La programmation fonctionnelle peut utiliser plus de mémoire.

exemple sur FP


Optionnels

Les options vous permettent de mettre rien à zéro. Cela réduira le nombre de références de classes ou désinitialisera les structures. Mettre les choses à zéro est le moyen le plus simple de nettoyer la mémoire. Cela va de pair avec ARC. Une fois que vous avez défini toutes les références d'une classe à zéro, cela désactive et libère de la mémoire.

Si vous ne créez pas d'instance comme facultative, les données resteront en mémoire jusqu'à la fin de la fonction de fermeture ou jusqu'à la fin de la classe de fermeture. Vous ne savez peut-être pas quand cela se produira. Les options vous permettent de contrôler ce qui reste en vie pendant combien de temps.


API MM

De nombreuses "fuites de mémoire" sont provoquées par Frameworks qui ont une fonction de "nettoyage" que vous n'avez peut-être pas appelée. Un bon exemple est UIGraphicsEndImageContext() Le contexte restera en mémoire jusqu'à ce que cette fonction soit appelée. Il ne nettoie pas lorsque la fonction qui a créé le contexte se termine ou lorsque l'image impliquée est définie sur nil.

Un autre bon exemple est le rejet de ViewControllers. Il peut être judicieux de se connecter à un VC puis de revenir à la séquence, mais la séquence crée en fait un VC. Une séquence de retour ne détruit pas un VC. Appelez dismissViewControllerAnimated() pour supprimer de mémoire.

Lisez les références de classe et vérifiez qu'il n'y a pas de fonctions de "nettoyage".


Si vous avez besoin d'instruments pour détecter une fuite, consultez l'autre réponse à cette question.

33
R Menke

enter image description here

cliquez sur le nom de votre application dans le coin supérieur droit de Xcode.

enter image description here

cliquez sur "modifier le schéma" dans le menu qui apparaît.

enter image description here

assurez-vous que 'RUN' est sélectionné sur le côté gauche, puis cliquez sur l'onglet Diagnostics en haut de la fenêtre.

sous l'en-tête "gestion de la mémoire", cochez la case "activer Guard Malloc"

vous pouvez également essayer de vérifier les "objets distribués" et la "pile malloc" sous l'en-tête "journalisation"

plus d'informations sur la garde malloc, les bords de garde et le gribouillage peuvent être trouvés ici .



J'espère que cela t'aides!

6
MoralCode