I\'m still learning my way through iOS development and working with Core Data and have just come across retain cycles.
It is my understanding from reading the Core Data
I've written a couple of helper methods (see below) to break the retain loops for a whole graph of objects by using introspection of the Entity model. You can use it after receiving a memory warning notification to release any memory held by the part of your core data model accessible through that particular object.
@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
My experience is that re-faulting only the department entity is enough to break the retain cycle. Profiling memory clearly shows that all related employees are then freed, unless they are retained elsewhere by your code.
As you can check at Breaking Relationship Retain Cycles, the retain cycles are necessary to prevent deallocation of unwanted objects. It means that you keep the the object retained while you are using it.
The refreshObject:mergeChanges
should be used if you are done with that object and you want to turn it into fault, to dispose memory if possible. It won't necessarily release the object in the other end of the relationship, it will only set a flag to core data that the object can be turned into fault if necessary.