La façon dont j'essaie de supprimer plusieurs sets de 10.000+ NSManagedObjects est trop gourmande en memory (environ 20 Mo en live) et mon application est en cours de largage. Voici l'implémentation de la méthode delete:
+ (void)deleteRelatedEntitiesInManagedObjectContext:(NSManagedObjectContext *)context { NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; [context setUndoManager:nil]; [fetch setEntity:[NSEntityDescription entityForName:NSSsortingngFromClass(self) inManagedObjectContext:context]]; [fetch setIncludesPropertyValues:NO]; NSError *error = nil; NSArray *entities = [context executeFetchRequest:fetch error:&error]; NSInteger deletedCount = 0; for (NSManagedObject *item in entities) { [context deleteObject:item]; deletedCount++; if (deletedCount == 500) { [context save:&error]; deletedCount = 0; } } if (deletedCount != 0) { [context save:&error]; } }
J'ai essayé: -setFetchBatchSize, mais il y a encore plus de memory utilisée.
Quel serait un moyen plus efficace de memory pour faire cela?
EDIT : Je viens de regarder WWDC 2015 " Quoi de neuf dans datatables de base " (c'est toujours la première video que je regarde, mais j'ai été très occupé cette année) et ils ont annoncé une nouvelle API: NSBatchDeleteRequest
qui devrait être beaucoup plus efficace que n'importe quelle solution précédente .
Efficace a plusieurs significations, et signifie le plus souvent une sorte de compromis. Ici, je suppose que vous voulez juste contenir de la memory pendant la suppression.
Les données de base ont beaucoup d'options de performance, au-delà de la scope de n'importe quelle question de SO.
La memory management dépend des parameters de vos managedObjectContext et fetchRequest. Regardez les docs pour voir toutes les options. En particulier, cependant, vous devriez garder ces choses à l'esprit.
Aussi, gardez à l'esprit l'aspect performance. Ce type d'opération doit être effectué sur un thread séparé.
De plus, notez que le rest du graphe d'objects entrera également en jeu (à cause de la façon dont CoreData gère la suppression des objects liés.
En ce qui concerne la consommation de memory, il y a deux propriétés sur MOC en particulier à prêter attention. Bien qu'il y ait beaucoup de choses ici, il est loin d'être exhaustif. Si vous voulez réellement voir ce qui se passe, NSLog votre MOC juste avant et après chaque opération de sauvegarde. En particulier, consignez registeredObjects et deletedObjects.
Le MOC a une list d'objects enregistrés. Par défaut, il ne conserve pas les objects enregistrés. Cependant, si retainsRegisteredObjects est YES, tous les objects enregistrés seront conservés.
Pour les suppressions en particulier, setPropagatesDeletesAtEndOfEvent indique au GPM comment gérer les objects liés. Si vous souhaitez les traiter avec la sauvegarde, vous devez définir cette valeur sur NON. Sinon, il attendra que l'événement en cours soit terminé
Si vous avez vraiment de grands sets d'objects, pensez à utiliser fetchLimit. Bien que les fautes ne prennent pas beaucoup de memory, elles en prennent quand même, et plusieurs milliers à la fois ne sont pas insignifiantes. Cela signifie plus de récupération, mais vous limiterez la quantité de memory
Considérez également, chaque fois que vous avez de grandes loops internes, vous devriez utiliser votre propre pool autorelease.
Si ce MOC a un parent, l'logging déplace uniquement ces modifications vers le parent. Dans ce cas, si vous avez un MOC parent, vous ne faites que le faire grandir.
Pour limiter la memory, considérez ceci (pas nécessairement le meilleur pour votre cas – il y a beaucoup d'options de données de base – vous seul savez ce qui est le mieux pour votre situation, basé sur toutes les options que vous utilisez ailleurs.
J'ai écrit une catégorie sur NSManagedObjectContext que j'utilise pour save quand je veux m'assurer que la sauvegarde va au backing store, très similaire à ceci. Si vous n'utilisez pas une hiérarchie MOC, vous n'en avez pas besoin, mais … il n'y a vraiment aucune raison de ne pas utiliser une hiérarchie (sauf si vous êtes lié à l'ancien iOS).
- (BOOL)cascadeSave:(NSError**)error { __block BOOL saveResult = YES; if ([self hasChanges]) { saveResult = [self save:error]; } if (saveResult && self.parentContext) { [self.parentContext performBlockAndWait:^{ saveResult = [self.parentContext cascadeSave:error]; }]; } return saveResult; }
J'ai modifié un peu votre code …
+ (void)deleteRelatedEntitiesInManagedObjectContext:(NSManagedObjectContext *)context { NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; [context setUndoManager:nil]; [fetch setEntity:[NSEntityDescription entityForName:NSSsortingngFromClass(self) inManagedObjectContext:context]]; [fetch setIncludesPropertyValues:NO]; [fetch setFetchLimit:500]; NSError *error = nil; NSArray *entities = [context executeFetchRequest:fetch error:&error]; while ([entities count] > 0) { @autoreleasepool { for (NSManagedObject *item in entities) { [context deleteObject:item]; } if (![context cascadeSave:&error]) { // Handle error appropriately } } entities = [context executeFetchRequest:fetch error:&error]; } }
Dans un moment d'inspiration, j'ai enlevé [fetch setIncludesPropertyValues:NO];
et c'était bon. De la docs:
Lors d'une extraction normale (includesPropertyValues est YES), Core Data récupère l'ID d'object et datatables de propriété pour les loggings correspondants, remplit le cache de lignes avec les informations et renvoie l'object géré comme défauts (voir returnsObjectsAsFaults). Ces erreurs sont des objects gérés, mais toutes leurs données de propriété résident toujours dans le cache de lignes jusqu'à ce que l'erreur soit déclenchée. Lorsque l'erreur est déclenchée, Core Data récupère datatables du cache de lignes: il n'est pas nécessaire de revenir à la database.
J'ai réussi à réduire les octets alloués à environ 13 Mo, ce qui est mieux.
NSBatchDeleteRequest Travaillé pour moi; réduit le time de suppression des objects gérés d'un facteur 5 sans pic de memory.
Ce serait un test intéressant, essayez d'utiliser Magical Record . Il y a une méthode truncate qui est censée être très efficace (je l'ai utilisée sur des sets de données aussi grands que 3000 loggings sans problème.
Je ne voudrais pas l'utiliser uniquement pour cette fonctionnalité, si vous ne l'avez pas essayé, vous devriez. Cela rend le traitement des données de base beaucoup plus facile et avec beaucoup less de code.
J'espère que cela t'aides.
Je ne l'ai jamais testé, mais si la memory est votre principale préoccupation, vous pouvez essayer d'encapsuler ces lots de 500 suppressions dans un pool d'autorelease supplémentaire. Il est possible que le context: save crée un certain nombre d'objects autoreleased qui ne sont pas libérés tant que le cycle de boucle d'exécution n'est pas terminé. Avec plus de 10 000 loggings, cela pourrait très bien s'append.
Si vous ne voulez pas utiliser une autre API, essayez une autre fonction de NSFetchRequest
, fetchLimit
, peut-être en conjonction avec fetchOffset
. Je l'ai vu dans l'un des cours iTunes U iPad dans un exemple impliquant un gros nombre croquant avec Core Data.
NSInteger limit = 500; NSInteger count = [context countForFetchRequest:fetch error:nil]; [fetch setFetchLimit:limit]; for (int i=0; i < count/limit+1; i++) { // do the fetch and delete and save }
Vous pouvez ajuster fetchLimit
pour satisfaire vos besoins en memory.