Amazon.com Widgets

shanecrawford.org Home Grown in Austin

Posted
9 April 2008 @ 4pm

Tagged
Development, Mac

Virtual Accessors in CoreData

In order to provide the best user experience it often occurs in a UI’s design that the underlying data model doesn’t quite mirror the mental model of the end user. Naturally, as a good UI designer you want to present to the end user what they expect to see and not force upon them your underlying implementation details. Fortunately, CoreData provides a fairly simple means of bridging this gap between the real and the ideal. This bridge over troubled waters is what might be known as virtual accessor methods

A Means to An End

One of the great things about CoreData is the ability to model your entity objects in XCode’s data modeler and automatically have access to its attributes and relationships via Objective-C 2.0 properties or the standard Key Value Coding (KVC) methods. Simply gain access to an NSManagedObject instance for your entity and away you go. This magic is pulled off by virtue of CoreData dynamically generating efficient accessor methods for the properties that you define. These internally generated methods take the form primitiveKey (e.g. for the property ‘title’ the method ‘primitiveTitle’ would be generated). CoreData then effectively injects itself into the KVC mechanisms by checking for unbounded property exceptions on calls to valueForKey: and then calling its own generated ‘primitive’ methods in turn.

Another very important part of the CoreData voodoo is that NSManagedObject disables automatic key value observing change notifications. In addition, the generated primitive methods do not fire these notifications for you. Therefore, it is very important for subclasses of NSManagedObject providing their own accessor method implementations to fire the relevant access and change notification methods whenever accessing entity properties. The important methods to note are:

- (void)willAccessValueForKey:(NSString *)key;
- (void)didAccessValueForKey:(NSString *)key;
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

Now, finally armed with knowledge of CoreData’s intimate details we can implement our own NSManagedObject accessor methods. More importantly, the properties that those accessor methods represent do not actually need to exist on the entity definition and we can bind to them in Interface Builder.

Virtually Yours

In order to demonstrate this wizardry we evoke our now ubiquitous blog application example. For this incarnation we want to automatically generate a title based on a posts author and date. A dubious example to be sure but it serves our purposes.

Our first order of business is to create a BlogPost entity using XCode’s data modeler. The entity will contain only two real properties, an ‘author’ property of type String and a ‘postDate’ property of type Date. The ‘title’ property will be considered as virtual and will automatically be created as a custom combination of the current value of the ‘author’ and ‘postDate’ attributes. Next, we need to create a subclass of NSManagedObject and assign that class to our entity (See CoreData’s default date value for details on doing this). The primary purpose of our subclass is two fold. First, it defines accessor methods for our virtual ‘title’ property. Second, it overrides the setter methods for ‘author’ and ‘postDate’ in order to notify the system that the ‘title’ property has changed whenever one of these two properties changes. If our title property worked in reverse and was write-only there would be no need to override the author and postDate setter methods. Here’s the code:

@implementation BlogPostEntity

- (NSString*) title
{
    [self willAccessValueForKey:@"author"];
    [self willAccessValueForKey:@"postDate"];

    NSString *titleAuthor = [self primitiveAuthor];
    NSDate *titleDate = [self primitivePostDate];

    [self didAccessValueForKey:@"author"];
    [self didAccessValueForKey:@"postDate"];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
    [dateFormatter setTimeStyle:NSDateFormatterNoStyle];

    NSString *title = [NSString stringWithFormat:@"%@ (%@)", [dateFormatter stringFromDate:titleDate], titleAuthor];

    [dateFormatter release];

    return title;
}

- (void)setAuthor:(NSString *)newAuthor
{
    [self willChangeValueForKey:@"author"];
    [self willChangeValueForKey:@"title"];

    [self setPrimitiveAuthor:newAuthor];

    [self didChangeValueForKey:@"author"];
    [self didChangeValueForKey:@"title"];
}

- (void)setPostDate:(NSDate *)newPostDate
{
    [self willChangeValueForKey:@"postDate"];
    [self willChangeValueForKey:@"title"];

    [self setPrimitivePostDate:newPostDate];

    [self didChangeValueForKey:@"postDate"];
    [self didChangeValueForKey:@"title"];
}
@end

Take particular note of the usage of willAccessValueForKey:, didAccessValueForKey:, willChangeValueForKey:, and didChangeValueForKey:. In the ‘title’ accessor we notify the system that we will be accessing the values of the author and postDate attributes. Likewise, in the setter methods for author and postDate we notify that we will be changing the requisite property along with title (which we are in effect changing). An additional item of interest are the calls to primitiveAuthor, primitivePostDate, setPrimitiveAuthor:, and setPrimitivePostDate:. These methods are dynamically generated by NSManagedObject and therefore are not known at compile time thus producing warnings. These warnings can easily be taken care of by creating a category which defines those methods.

@interface BlogPostEntity (PrimitiveAccessors)
- (NSString *)primitiveAuthor;
- (void)setPrimitiveAuthor:(NSString *)newAuthor;
- (NSDate *)primitivePostDate;
- (void)setPrimitivePostDate:(NSDate *)newPostDate;
@end

As you can see nowhere did we define an actual ‘title’ property on our BlogPost entity in the data modeler. Yet, we can now bind to that property in Interface Builder and allow our interface to diverge from the underlying data model when needed.

For those that want to take a closer look the example XCode project can be found here: Virtual Accessors Example Project. For the rest, keep this little technique in mind for those occasions when you know that the user interface should be this way or that but you just don’t want to torque your data model. Code on.

Update

Scott Stevenson has provided an improvement to this technique. The end result is the same but the implementation is more elegant and much more maintainable. In essence, instead of overriding the accessor methods for author and postDate we provide an implementation for +keyPathsForValuesAffectingTitle. This lets the system know that a modification to either ‘author’ or ‘postDate’ should also trigger a change to ‘title’ thus eliminating the need to override their accessor methods. Sweet. In addition, since we still need to access the values for author and postDate we go ahead and define them as Objective-C 2.0 properties of our NSManagedObject subclass with the implementation tagged as @dynamic. This prevents compiler errors while still allowing us to access the values as provided by our superclass. Very nice indeed. An updated project can be found here: Virtual Accessors Example Project Improved


2 Comments

Posted by
Adam
3 August 2009 @ 3pm

Thanks, very helpful post. One question: can a virtual accessor be used as the sort key for a NSFetchRequest? Or do I need to create a real attribute that gets set whenever the related attributes are changed?


Posted by
Schedule
29 October 2010 @ 8pm

Best you could make changes to the webpage name shanecrawford.org – Virtual Accessors in CoreData to more generic for your subject you write. I enjoyed the post all the same.


Leave a Comment