[Résolu] - Core Data et @autoreleasepool
skimpy
Membre
Bonjour,
Je récupère environ 100000 lignes d'un serveur que je dois stocker dans Core Data. J'ai une première méthode qui lit les données, les splitte et les ajoute à un dictionnaire. Lorsque la lecture réseau est terminée, le dictionnaire est parcouru, je créé des Managed Object et tous les 1000 objets créés, je fais un save du Managed Object Context. Le problème est que l'utilisation mémoire augmente très rapidement (je passe de 60 MB à 120MB entre l'entrée et la sortie de la méthode). J'ai ajouté un @autoreleasepool à ma méthode mais j'ai l'impression qu'il ne fait rien.
@autoreleasepool {
__block int numGroups = 0;
__block int numUnsavedGroups = MAX_UNSAVED_GROUPS_BEFORE_SAVE;
[self.groupProperties enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
MyGroup *newGroup = (MYGroup *)[NSEntityDescription insertNewObjectForEntityForName:@Group inManagedObjectContext:self.moc];
newGroup.name = (NSString *)key;
newGroup.low = [obj objectAtIndex:0];
numGroups++;
numUnsavedGroups++;
if (numUnsavedGroups >= MAX_UNSAVED_GROUPS_BEFORE_SAVE) {
NSError *error;
if (![self.moc save:&error]) {
NSLog(@Unresolved error %@, %@", error, [error userInfo]);
abort();
}
numUnsavedGroups = 0;
}
}];
}
Est-ce que j'ai loupé une étape ? Comment faire pour réduire cette utilisation mémoire ?
Pour info, je suis en ARC.
Merci.
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
J'ai l'impression que 60Mo c'est la taille totale de tes données. Elles sont stockées une fois dans un dictionnaire puis une seconde fois sous CoreData, donc il est normal que cela occupe environ le double de la taille totale.
Ensuite, j'imagine que tu libèreras le dictionnaire, et là tu récupèreras tes 60Mo.
Je libère le dictionnaire mais la mémoire ne semble pas être rendue. Dans ma classe, j'ai ajouté la méthode dealloc dans laquelle je fais un :
NSLog(@dealloc called);
self.groupProperties = nil;
Je le vois bien passer dans le dealloc.
Et si tu mets ton @autoreleasepool à l'intérieur de l'énumération, plutôt, c'est à dire à l'intérieur du block, pour qu'elle soit purgée à chaque itération ? Parce que bon à l'extérieur ça n'a pas trop d'utilité, ça va attendre d'avoir itéré sur tous les self.groupProperties avant de purger, donc ça va pas libérer grand chose en mémoire avant la fin de la boucle !
J'ai essayé de mettre l'autoreleasepool dans l'enumeration mais ça ne change pas (d'ailleurs, ma première version de méthode était faite comme ça).
Et bien alors c'est que ça marche bien sans doute : 60 Mo ça doit être l'empreinte mémoire de ton application + 60 Mo de données sous Core Data.
En fait, au démarrage de l'application, elle consomme 33MB. Lorsque la 1ère méthode qui va récupérer les données via le réseau et qui construit le dictionnaire a fini son traitement, je passe à ~69MB et lorsque j'intègre les données dans Core Data, je passe à ~120MB.
Tout se passe comme si les données dans un dictionnaire occupaient 36 Mo, et les mêmes données sous CoreData occupent 87 Mo soit 2,5 fois plus. ça fait beaucoup mais ce n'est pas totalement délirant.
As tu essayé d'éliminer la pile CoreData après le dernier Save, et en particulier le contexte, pour voir combien tu récupères d'espace mémoire ?
Pour la taille, je ne m'inquiète pas trop car en fait mon Entité sous Core Data a 3 attributs de plus que mon dictionnaire (comme je suis en phase de test, j'assignais seulement quelques valeurs pour voir le résultat).
Par éliminer la pile Core Data, est-ce que tu veux dire faire un reset du ManagedObjectContext ([self.moc reset])lorsque l'énumération est terminée ?
Oui ça devrait suffire.
En effet, le reset libère la mémoire par contre ça a un effet plutôt négatif sur le reste de l'application. Je pense que la solution consiste à créer un ManagedObjectContext temporaire ... je vais essayer ça !
Il ne faut surtout pas laisser le reset. C'était juste pour évaluer la mémoire occupée par tes données sous Core Data, pour vérifier que ton dictionnaire est bien libéré.
En fait il faut laisser CoreData gérer la mémoire tout seul, il est fait pour ça.
Merci jpimbert et Ali pour vos réponses. J'ai lu la doc d'Apple sur les imports dans Core Data et Apple recommande de créer un autre Managed Object Context et de mettre son undoManager à nil. Je l'ai fait et là je reste à environ 70MB.
Je confirme car j'ai une application où je fais le même genre d'import (désolé de ne pas avoir vu le sujet avant):
Créer un context temporaire,
faire un autorelease à chaque itération,
et faire un save+reset de temps en temps (et en cas de memory warning).
Après tu auras peut-être un problème de vitesse car l'import est très lent sous CoreData, donc tu vas finir par mettre ce code dans une tâche d'arrière plan pour afficher un indicateur d'activité ou une progress bar.