2010-10-21 26 views
14

Todavía estoy aprendiendo a través del desarrollo de iOS y trabajando con Core Data, y acabo de encontrar ciclos de retención.Datos principales: evitando retener ciclos en muchas relaciones

Según mis conocimientos, al leer la Guía de programación de datos básicos, una vez que haya terminado de trabajar con una relación, utilice el método refreshObject:mergeChanges del contexto del objeto administrado para asegurarse de que el ciclo de retención esté interrumpido.

Digamos que tengo una relación demasiado grande entre un Departamento y sus Empleados, y en mi código accedo a la relación de empleados del departamento, ¿eso significa que ahora tendré que recorrer cada objeto del empleado y llamar al refreshObject:mergeChanges ¿método? En el código que esto sería

for (Employee *anEmployee in department.employees) { 
    //some code that accesses an employee's properties 

    [context refreshObject:enEmployee mergeChanges:NO]; 
} 

Parece que si yo no hago eso, cada objeto empleado accedo ahora contendrá una referencia al departamento y que va a terminar con los ciclos de retener.

¿Es correcto mi comprensión aquí? ¿Es este un enfoque estándar cuando se trata de muchas relaciones en Core Data? Gracias.

Respuesta

1

Como puede comprobar en Breaking Relationship Retain Cycles, los ciclos de retención son necesarios para evitar la desasignación de objetos no deseados. Significa que mantengas el objeto retenido mientras lo estás usando.

El refreshObject:mergeChanges se debe utilizar si ha terminado con ese objeto y desea convertirlo en error, para eliminar la memoria si es posible. No necesariamente liberará el objeto en el otro extremo de la relación, solo establecerá un indicador para los datos centrales que el objeto puede convertirse en error si es necesario.

+0

Esto significa que cada vez que trabajo con los objetos de la relación, ¿tendré que llamar a ese método sobre el objeto? –

+0

No es obligatorio hacerlo, aunque es muy recomendable en el caso de que tenga una tienda considerablemente grande, y acceda frecuentemente a 'NSManagedObjects', y no tenga la necesidad de guardarlos en la memoria (puede dejar que se conviertan en fallas)) Imagine CoreData como un "sistema de recolección de basura", en un entorno recolectado de basura solo dispone de memoria si le preocupa el uso de la memoria. El 'refreshObject: mergeChanges' liberaría el objeto en su lugar para dejarlo al sistema del recolector de basura. Este material del recolector de basura es solo una analogía para que comprendas el proceso. – vfn

3

He escrito un par de métodos de ayuda (ver a continuación) para romper los bucles de retención para un gráfico completo de objetos mediante el uso de la introspección del modelo de entidad. Puede usarlo después de recibir una notificación de advertencia de memoria para liberar cualquier memoria en poder de la parte de su modelo de datos central accesible a través de ese objeto en particular.

@interface CoreDataHelper(Private) 

+ (void)faultObjectImpl:(NSManagedObject *)managedObject mergeChanges:(FaultChangeBehaviour)mergeChanges; 
+ (void)faultObjectGraphForObject:(NSManagedObject *)managedObject handledObjects:(NSMutableArray *)handledObjects mergeChanges:(FaultChangeBehaviour)mergeChanges; 

@end 

@implementation CoreDataHelper 

typedef enum FaultChangeBehaviour { 
    FaultChangeBehaviourIgnore, 
    FaultChangeBehaviourReapply, 
    FaultChangeBehaviourMerge 
} FaultChangeBehaviour; 



+ (void)faultObjectGraphForObject:(NSManagedObject *)managedObject keepChanges:(BOOL)keepChanges { 
    NSMutableArray *handledObjects = [NSMutableArray arrayWithCapacity:64]; 
    FaultChangeBehaviour mergeBehaviour = keepChanges ? FaultChangeBehaviourReapply : FaultChangeBehaviourIgnore; 
    [self faultObjectGraphForObject:managedObject handledObjects:handledObjects mergeChanges:mergeBehaviour]; 
} 

+ (void)refreshObject:(NSManagedObject *)managedObject { 
    [self faultObjectImpl:managedObject mergeChanges:FaultChangeBehaviourMerge]; 
} 

+ (void)refreshObjectGraphForObject:(NSManagedObject *)managedObject { 
    NSMutableArray *handledObjects = [NSMutableArray arrayWithCapacity:64]; 
    [self faultObjectGraphForObject:managedObject handledObjects:handledObjects mergeChanges:FaultChangeBehaviourMerge]; 
} 

@end 

@implementation CoreDataHelper(Private) 

+ (void)faultObjectImpl:(NSManagedObject *)managedObject mergeChanges:(FaultChangeBehaviour)mergeChanges { 
    //Only fault if the object is not a fault yet and is not in a modified state or newly inserted (not saved yet) 
    BOOL isFault = [managedObject isFault]; 
    BOOL isTemporary = [[managedObject objectID] isTemporaryID]; 
    BOOL isUpdated = [managedObject isUpdated]; 

    NSDictionary *changedValues = [managedObject changedValues]; 

    if (isUpdated && (mergeChanges == FaultChangeBehaviourIgnore)) { 
     NSLog(@"Warning, faulting object of class: %@ with changed values: %@. The changes will be lost!", 
       NSStringFromClass([managedObject class]), changedValues); 
    } 

    if (!isFault && !isTemporary) { 
     [[managedObject managedObjectContext] refreshObject:managedObject mergeChanges:(mergeChanges == FaultChangeBehaviourMerge)]; 
     if (mergeChanges == FaultChangeBehaviourReapply) { 
      for (NSString *key in changedValues) { 
       id value = [changedValues objectForKey:key]; 
       @try { 
        [managedObject setValue:value forKey:key]; 
       } @catch (id exception) { 
        NSLog(@"Could not reapply changed value: %@ for key: %@ on managedObject of class: %@", value, key, NSStringFromClass([managedObject class])); 
       } 

      } 
     } 
    } 
} 

+ (void)faultObjectGraphForObject:(NSManagedObject *)managedObject handledObjects:(NSMutableArray *)handledObjects mergeChanges:(FaultChangeBehaviour)mergeChanges { 

    if (managedObject != nil && ![managedObject isFault] && ![handledObjects containsObject:[managedObject objectID]]) { 
     [handledObjects addObject:[managedObject objectID]]; 
     NSEntityDescription *entity = [managedObject entity]; 

     NSDictionary *relationShips = [entity relationshipsByName]; 
     NSArray *relationShipNames = [relationShips allKeys]; 

     for (int i = 0; i < relationShipNames.count; ++i) { 
      NSString *relationShipName = [relationShipNames objectAtIndex:i]; 
      if (![managedObject hasFaultForRelationshipNamed:relationShipName]) { 
       id relationShipTarget = [managedObject valueForKey:relationShipName]; 
       NSRelationshipDescription *relationShipDescription = [relationShips objectForKey:relationShipName]; 

       if ([relationShipDescription isToMany]) { 
        NSSet *set = [NSSet setWithSet:relationShipTarget]; 
        for (NSManagedObject* object in set) { 
         [self faultObjectGraphForObject:object handledObjects:handledObjects mergeChanges:mergeChanges]; 
        } 
       } else { 
        NSManagedObject *object = relationShipTarget; 
        [self faultObjectGraphForObject:object handledObjects:handledObjects mergeChanges:mergeChanges]; 
       } 
      } 
     } 

     [self faultObjectImpl:managedObject mergeChanges:mergeChanges]; 
    } 
} 

@end 
+0

¡Gracias! Esto se ve muy útil. No responde mi pregunta inicial, así que no la aceptaré como una respuesta, pero igualmente te agradezco. –

+0

¿Es esto realmente necesario? Pensé que los datos centrales responderían automáticamente a la advertencia de memoria. ¿No son estos métodos de ayuda solo creando una sobrecarga cuando se genera una advertencia de memoria? – vfn

+0

Desafortunadamente, los datos principales no pueden liberar automáticamente los objetos que forman parte de un bucle de retención (es decir, sin recogida automática de basura, que no está disponible en el iPhone), primero debe romper los bucles de retención reiniciando el contexto o utilizando refreshObject: mergeChanges :Sin llamadas. –

0

Mi experiencia es que volviendo a fallar solo la entidad del departamento es suficiente para romper el ciclo de retención. La memoria de perfil muestra claramente que todos los empleados relacionados se liberan, a menos que el código los conserve en otra parte.

Cuestiones relacionadas