核心数据:避免在多对多关系中保留周期

时间:2010-10-21 04:13:34

标签: core-data memory-management ios

我仍在通过iOS开发学习并使用Core Data,并且刚刚遇到了保留周期。

通过阅读“核心数据编程指南”,我的理解是,在完成关系之后,使用托管对象上下文方法refreshObject:mergeChanges来确保保留周期被破坏。

因此,假设我在部门与其员工之间存在多对多的关系,并且在我的代码中,我从部门访问员工关系,这是否意味着我现在需要遍历每个员工对象并调用{{ 1}}方法?在代码中,这将是

refreshObject:mergeChanges

似乎如果我不这样做,我访问的每个员工对象现在都会包含对部门的引用,我将最终保留周期。

我的理解在这里是否正确?在处理Core Data中的多对关系时,这是一种标准方法吗?感谢。

3 个答案:

答案 0 :(得分:3)

我编写了几个辅助方法(见下文),通过使用Entity模型的内省来打破整个对象图的保留循环。您可以在收到内存警告通知后使用它,以释放通过该特定对象可访问的核心数据模型部分所拥有的任何内存。

@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

答案 1 :(得分:1)

正如您可以在Breaking Relationship Retain Cycles处查看,保留周期是必要的,以防止不需要的对象的重新分配。这意味着您在使用对象时保留对象。

如果您已完成该对象并且想要将其变为错误,则应使用refreshObject:mergeChanges,以便在可能的情况下处置内存。它不一定会在关系的另一端释放对象,它只会为核心数据设置一个标志,以便在必要时将对象转换为错误。

答案 2 :(得分:0)

我的经验是,只有部门实体的重新断层足以打破保留周期。分析内存清楚地显示所有相关员工随后被释放,除非他们通过您的代码保留在其他地方。