Core Data: Cached Transient Properites
In writing HostManager 2.0, I’ve come up against a lot of problems caused by the dark magic that is Cocoa bindings and key-value observing. One of the most troublesome of these has been with transient (calculated) properties.
For example, a Client managed object can get a count of owned domains that are expiring soon. This is a transient propety, accessed through a key of expiringDomains. It simply returns an NSSet which is built from a fetchRequest.
Previously, I was running this fetchRequest each time the key was accessed. This was admittedly inefficient, but seemed functional - at least until you saved the document.
Something crazy happens when a Core Data document is saved - all the objects in a context are released (faulted). There is a good reason for this, as it frees up any memory that was being used to temporarily store the managed objects. However, it caused very strange results to appear in my transient properties; namely, they would all vanish.
Even forcing the fetch request to refresh didn’t pick up any objects from the context (I’m not sure why as I would have expected the faulted objects to be ‘refired’). So I took the plunge and refactored the Client object to cache its expiringDomains value. After some extensive testing and finger-crossing, I’m fairly sure this is the correct way to handle transient relationship properties:
CustomManagedObject.h
@interface CustomManagedObject : NSManagedObject { ame NSSet *transientRelationship; } - (NSSet *)transientRelationship;
CustomManagedObject.m
#import "CustomManagedObject.h" @implementation CustomManagedObject - (void)awakeFromFetch { ame [super awakeFromFetch]; ame // set managed object to observe changes to the context, this is necessary ame // to monitor changes to the context that occur elsewhere in the app ame [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refresh:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]]; ame // clear the cache after managed object is fetched into the context ame if( transientRelationship != nil ) ame [transientRelationship release], transientRelationship = nil;} - (void)refresh:(NSNotification *)notification { ame // ... perform check to see if update is neccessary, assume it is... ame if( refreshNeeded ) { ame if( expiringDomains != nil ) ame gDomains release], expiringDomains = nil; ame } } - (NSSet *)transientRelationship { ame [self willAccessValueForKey:@"transientRelationship"] ame ame if( transientRelationship == nil ) {SFetchRequest *aFetchRequest = [[[NSFetchRequest alloc] init] autorelease]; ame [aFetchRequest setEntity:[NSEntityDescription entityForName:@"DestinationEntity" inManagedObjectContext:[self managedObjectContext]]]; ame // optionally, set a predicate, e.g. owner == self ame [aFetchRequest setPredicate:[NSPredicate predicateWithFormat:@"predicateString"]]; ame NSArray *results = [[self managedObjectContext] executeFetchRequest:aFetchRequest error:nil]; ame transientRelationship = [[NSSet setWithArray:results] retain]; ame } ame ame [self didAccessValueForKey:@"transientRelationship"] ame return transientRelationship; }
Tags: bindings, cocoa, code, coredata, development, HostManager, Programming