1 November 2007 · About 2 minutes read

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

```objc@interface CustomManagedObject : NSManagedObject { NSSet *transientRelationship;

}

  • (NSSet *)transientRelationship;```

CustomManagedObject.m

```objc#import “CustomManagedObject.h”

@implementation CustomManagedObject

  • (void)awakeFromFetch

{ [super awakeFromFetch]; // set managed object to observe changes to the context, this is necessary // to monitor changes to the context that occur elsewhere in the app [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refresh:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]]; // clear the cache after managed object is fetched into the context if( transientRelationship != nil ) [transientRelationship release], transientRelationship = nil;}

  • (void)refresh:(NSNotification *)notification

{ // … perform check to see if update is neccessary, assume it is… if( refreshNeeded ) { if( expiringDomains != nil ) gDomains release], expiringDomains = nil; }

}

  • (NSSet *)transientRelationship

{ [self willAccessValueForKey:@”transientRelationship”]

if( transientRelationship == nil ) {SFetchRequest *aFetchRequest = [[[NSFetchRequest alloc] init] autorelease]; [aFetchRequest setEntity:[NSEntityDescription entityForName:@”DestinationEntity” inManagedObjectContext:[self managedObjectContext]]]; // optionally, set a predicate, e.g. owner == self [aFetchRequest setPredicate:[NSPredicate predicateWithFormat:@”predicateString”]]; NSArray *results = [[self managedObjectContext] executeFetchRequest:aFetchRequest error:nil]; transientRelationship = [[NSSet setWithArray:results] retain]; }

[self didAccessValueForKey:@”transientRelationship”] return transientRelationship;

}```

Chris Blunt
Chris Blunt @cblunt
Chris is the founder of Plymouth Software. As well as code and business, he enjoys being a Dad, swimming, and the fine art of drinking tea.