2012-05-14 14 views
6

La forma en que trato de eliminar varios conjuntos de 10.000+ NSManagedObjects requiere demasiada memoria (alrededor de 20 MB de bytes activos) y mi aplicación se está descartando. Aquí está la implementación del método de eliminación:¿Cuál es la forma más eficiente de eliminar un número grande (más de 10.000) objetos en Core Data?

+ (void)deleteRelatedEntitiesInManagedObjectContext:(NSManagedObjectContext *)context 
{ 
    NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; 
    [context setUndoManager:nil]; 

    [fetch setEntity:[NSEntityDescription entityForName:NSStringFromClass(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]; 
    } 
} 

He intentado: -setFetchBatchSize, pero hay aún más memoria utilizada.

¿Cuál sería una forma más eficiente de memoria para hacer esto?

+0

Optimizaciones que funcionaron: 1. eliminar la línea -setIncludesPropertyValues; 2. cambiar el límite de búsqueda de 500 a 2500 (¡en serio!); 3.use @autoreleasepool –

Respuesta

11

EDITAR: Acabo de ver 2015 WWDC "¿Qué hay de nuevo en la base de datos" (que siempre es el primer video que observo, pero he estado muy ocupado este año) y se anunció una nueva API: NSBatchDeleteRequest que debe ser mucho más eficiente que cualquier solución anterior.


Eficiente tiene múltiples significados, y la mayoría de las veces significa algún tipo de compensación. Aquí, supongo que solo quieres contener memoria mientras borras.

Datos principales tiene lotes de opciones de rendimiento, más allá del alcance de cualquier pregunta SO.

El manejo de la memoria depende de la configuración de managedObjectContext y fetchRequest. Mire los documentos para ver todas las opciones. En particular, sin embargo, debes tener esto en cuenta.

Además, tenga en cuenta el aspecto del rendimiento. Este tipo de operación debe realizarse en un hilo separado.

Además, tenga en cuenta que el resto de su gráfico de objetos también se pondrá en juego (por la forma en CoreData maneja eliminación de objetos relacionados.

En cuanto al consumo de memoria, hay dos propiedades en MOC en particular, a prestar atención a .Si bien hay mucho aquí, de ninguna manera es similar a exhaustivo. Si quiere ver realmente lo que está sucediendo, NSLog su MOC justo antes y después de cada operación de guardado. En particular, registre Objects registrados y deletedObjects.

  1. El MOC tiene una lista de objetos registrados. Por defecto, no retiene los objetos registrados. Sin embargo, si retainsRegisteredObjects es SÍ, retendrá todos los objetos registrados.

  2. Para las eliminaciones en particular, setPropagatesDeletesAtEndOfEvent le dice al MOC cómo manejar los objetos relacionados. Si desea que se manejen con el guardado, debe establecer ese valor en NO. De lo contrario, esperará hasta que se complete el evento actual

  3. Si tiene conjuntos de objetos realmente grandes, considere usar fetchLimit. Si bien las fallas no requieren mucha memoria, todavía toman algunas y muchas miles a la vez no son insignificantes. Significa más búsqueda, pero limitará la cantidad de memoria

  4. También considere, cada vez que tenga bucles internos grandes, debería utilizar su propio grupo de liberación automática.

  5. Si este MOC tiene un padre, guardar solo mueve los cambios al padre. En este caso, si tiene un MOC padre, solo lo está haciendo crecer.

Para restringir la memoria, considere esto (no necesariamente mejor para su caso - hay un montón de opciones de Datos Básicos - sólo usted sabe lo que es mejor para su situación, sobre la base de todas las opciones que te en otro lugar.

Escribí una categoría en NSManagedObjectContext que uso para guardar cuando quiero asegurarme de que el guardado va al almacén de respaldo, muy similar a esto. Si no usa una jerarquía MOC, no lo hace lo necesito, pero ... realmente no hay razón para NO usar una jerarquía (a menos que esté vinculado al iOS anterior).

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

he modificado su código un poco ...

+ (void)deleteRelatedEntitiesInManagedObjectContext:(NSManagedObjectContext *)context 
{ 
    NSFetchRequest *fetch = [[NSFetchRequest alloc] init]; 
    [context setUndoManager:nil]; 

    [fetch setEntity:[NSEntityDescription entityForName:NSStringFromClass(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]; 
    } 
} 
+0

¡Gran respuesta, gracias! –

+0

Al usar el grupo de autorrelease, reduje el uso de memoria de 20MB a solo 8MB. El guardar en cascada es un poco exagerado para mí, pero lo recordaré para más adelante. También eliminé la línea -setIncludesPropertyValues ​​y usé el límite de búsqueda. –

+0

Tengo [una pregunta] (http://stackoverflow.com/questions/42675933/nsmanagedobjectcontexts-propagatesdeletesatendofevent-set-to-false-causes-error) relacionado con el uso de 'propagatesDeletesAtEndOfEvent'. – LShi

0

Esta sería una prueba interesante, intente utilizar Magical Record. Hay un método truncado que se supone que es muy eficiente (lo he usado en conjuntos de datos tan grandes como 3000 registros sin problema. Sería interesante ver cómo maneja 10,000.

No lo usaría simplemente para esa característica por sí sola, si no lo has probado, usted debe. Hace que se trata de datos básicos de manera mucho más fácil y mucho menos con el código.

Espero que esto ayude.

+0

Gracias, lo intentaré en algún momento. –

+0

Estoy haciendo lo siguiente con unos 16k registros en CoreData y la aplicación está fallando. Problemas de memoria. [MagicalRecord saveWithBlock:^(NSManagedObjectContext * localContext) { [Artículo MR_truncateAllInContext: localContext]; } finalización:^(error BOOL, error NSError *) { if (! IsEmpty (bloque)) { bloque (error); } }]; –

0

tengo de ninguna manera lo probó , pero si la memoria es su principal preocupación, puede tratar de encapsular esos lotes de 500 eliminaciones en un pool de autorelease adicional. Es posible que context: save cree bastantes objetos liberados automáticamente que no se liberan hasta que haya terminado con el ciclo de ejecución ciclo. Con más de 10 000 registros, podría sumar bastante bien.

+0

Gracias! Voy a intentar esto también. –

0

Si no desea utilizar otra API, pruebe con otra característica de NSFetchRequest, fetchLimit, tal vez en conjunto con fetchOffset. Lo he visto en uno de los cursos de iPad de iTunes U en un ejemplo que implica un gran número de crujidos con 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 
} 

Se puede ajustar el fetchLimit para satisfacer sus requerimientos de memoria.

+0

¿Recuerdas por casualidad qué curso era ese? Estoy interesado en verlo ... De todos modos, también tengo tu solución. –

1

En un momento de inspiración, quité [fetch setIncludesPropertyValues:NO]; y que era bueno. A partir de los documentos:

Durante una normal de fetch (includesPropertyValues ​​es sí), datos básicos obtiene los datos de identificación del objeto y de propiedad de los registros coincidentes, llena el caché fila con la información, y vuelve objeto gestionado como fallas (ver returnsObjectsAsFaults). Estas fallas son objetos gestionados , pero todos sus datos de propiedades aún residen en el caché de fila hasta que se dispara la falla. Cuando se activa la falla, Core Data recupera los datos de la memoria caché de filas; no es necesario volver a la base de datos .

Logré reducir los bytes vivos asignados a ~ 13MB, que es mejor.

1

NSBatchDeleteRequest Funcionó para mí; redujo el tiempo de eliminación de objetos administrados por un factor de 5, sin picos de memoria.

Cuestiones relacionadas