Core Data: Transient Properties on NSManagedObject

There seems to be little written on the subject of transient properties, which can lead to frustration. Having been frustrated by transient properties, here is a little summary in the hope that it helps others.

Transient properties are properties on a NSManagedObject that are not persisted to the object store. They are calculated at runtime, usually on the basis of other property values. The classic example would be for a Person object, generating a fullName property that is not persisted to the object store as both firstName and lastName are, so it can be composed at runtime. Here is a bit of sample code:

@interface Person : NSManagedObject

@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) NSString *fullName;

@end

@implementation Person

@dynamic firstName;
@dynamic lastName;
@dynamic fullName;

@end

The above code is as generated from the Core Data modeller in Xcode. The only thing to note here is that the fullName property includes the transient option, and as a result the underlying SQLite table will not contain a fullName column. See the section below about Non-Standard Property types if you want to store another class of object.

Our best bet is now to create a category on our Person object so that we are free to regenerate the Person.m/.h files if we make future model changes.

@interface Person (FullName)

@end

@implementation Person (FullName)

@end

The first thing to do is support the idea of reading the fullName property, so we will add a method to the implementation such as:

-(NSString *)fullName {

    [self willAccessValueForKey:@"fullName"];

    NSString *fullName = [@[self.firstName, self.lastName] componentsJoinedByString:@" "];

    [self didAccessValueForKey:@"fullName"];

}

The willAccessValueForKey: and didAccessValueForKey: are necessary for KVO compliance, so remember to add these for all transient getters (and non-transient for that matter if you are overriding the default).

Given that NSManagedObjects are inherently readwrite for all properties, it would be pertinent to add a setter for fullName so that it can be used in reverse. You could of course manually alter the class interface to declare the property as readonly but you’ll need to remember to repeat the alteration if you change the model and regenerate the interface and implementation files.

NB: I’m deliberately skipping error checking here for the sake of clarity on the issues pertinent to transient properties. You’d obviously want to handle edge cases like nil parameters and names with no whitespace or middle names.

-(void)setFullName:(NSString *)fullName {

    [self willChangeValueForKey:@"fullName"];

    NSArray *names = fullName componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet];

    self.firstName = names[0];
    self.lastName = [names lastObject];

    [self didChangeValueForKey:@"fullName"];
}

Note that in the above, the KVO notifications will be generated for firstName and lastName as we have used the standard getters and setters to make the change.

A second thing to notice is that we’ve not used any ivars. The @dynamic property declaration means that the compiler ignores the missing getters/setters and ivar, because we are telling it they will be created at runtime. It’s the counterpart to the @property declaration that tells the implementation that its ok that they are not explicitly defined.

Changing the components properties changes the composite

In order to fulfil our obligations to KVO, we need to consider what happens when either firstName or lastName change. If we have another part of the process observing changes to the fullName property, then a change to firstName or lastName will cause fullName to change as well, but at the moment there is no mechanism in place to generate that notification. As a result, we need to override the setters for firstName and lastName to provide that notification.



-(void)setFirstName:(NSString *)firstName {

    [self willChangeValueForKey:@"firstName"];
    [self willChangeValueForKey:@"fullName"];

    [self setPrimativeValue: firstName forKey:@"firstName"];

    [self didChangeValueForKey:@"firstName"];
    [self didChangeValueForKey:@"fullName"];
}

-(void)setLastName:(NSString *)lastName {

    [self willChangeValueForKey:@"lastName"];
    [self willChangeValueForKey:@"fullName"];

    [self setPrimativeValue: lastName forKey:@"lastName"];

    [self didChangeValueForKey:@"lastName"];
    [self didChangeValueForKey:@"fullName"];
}

The above ensures that when firstName or lastName are modified that a notification is also sent for fullName.

The Apple documentation refers to a +(NSSet*)keyPathsForValuesAffectingValuesWithKey: method that allows classes to inform KVO of such behaviour, but this is not available when implementing these additions via categories (you can use it in a subclass). It also mentions this fact at that it should be possible to implement +(NSSet*)keyPathsForValuesAffecting<Key> in a category (where <Key> is the transient property name), but in my experience this does not work. As a result, I’ve stuck with the above solution to notifications, even though it may be more longwinded.

What About Transient Non-Standard Property Types, such as User-Defined Classes or Cocoa Classes such as CLLocation?

The Core Data documentation talks at some length about Non-Standard Persistent Properties, but buried in this section under ‘An Object Attribute‘ are the clues about how to implement transient non-standard, non-persistent properties as well.

This was my specific scenario I worked through in coming up with this solution. I had a managed object that was principally based on Core Location’s CLLocation object. I wanted to store instances of CLLocation but also other properties, but not lose access to other CLLocation functionality, such as the ability to measure the distance between two points.

CLLocation is an immutable object, so we need to manage its recreation if any of the related managed object properties are changed. Noting that coreLocation is only needed when referenced, we can invalidate it as each property is set, and recreate it should it be accessed, avoiding unnecessary overhead.

As a result, I need an object similar to the following:

@interface MyLocation : NSManagedObject

@property (nonatomic, retain) NSNumber *latitude;
@property (nonatomic, retain) NSNumber *longitude;
@property (nonatomic, retain) id coreLocation;
// ... other properties

@end

You’ll notice that in the above, the coreLocation property is typed as id. This is because in the Core Data Modeller, I’ve chosen the transient and the transformable attributes. Remember that transient in going to prevent Core Data from storing the property value in the persistent store, but its downside is that we can’t specify a custom class. If you generated the class from the model, you’d have UNDEFINED_TYPE as the type. If on the other hand you specify transformable in addition, then you get a property of type id, and as long as you leave the name of the value transformer blank, no other side effects.

If you want to manually edit your managed object interfaces, rather than using the generator, then you could probably skip this step.

In the above interface, I want to keep the coreLocation property in sync with the managed objects attributes. As a result, I need to define customer getters/setters for each of the affected properties.

@implementation MyLocation (CLLocation)

-(void)setLatitude:(NSNumber *)latitude {

    [self willChangeValueForKey:@"latitude"];
    [self willChangeValueForKey:@"coreLocation"];

    [self setPrimitiveValue:latitude forKey:@"latitude"];
    [self setPrimitiveValue:nil forKey:@"coreLocation"];

    [self didChangeValueForKey:@"latitude"];
    [self didChangeValueForKey:@"coreLocation"];

}

// Repeat the above for each property

-(void)setCoreLocation:(id)coreLocation {

    [self willChangeValueForKey:@"coreLocation"];

    CLLocation *location = coreLocation;

    self.latitude = @(location.coordinate.latitude);
    self.longitude = @(location.coordinate.longitude);
    // ... repeat for other CLLocation properties

    [self didChangeValueForKey:@"coreLocation"];

}

Note that in the above setter for coreLocation there is no need to issue KVO notifications for coreLocation, as they will be sent by each of the dependent property changes (although I recognise that there is some duplication here that could be optimised).

-(id)coreLocation {

    [self willAccessValueForKey:@"coreLocation"];

    CLLocation *location = [self primitiveValueForKey:@"coreLocation"];

    if (location == nil) {

        location = [[CLLocation alloc] initWithCoordinate:
            CLLocationCoordinate2DMake([self.latitude doubleValue], 
                [self.longitude doubleValue]) 
            altitude:[self.altitude doubleValue] 
            horizontalAccuracy:[self.horizontalAccuracy doubleValue] 
            verticalAccuracy:[self.verticalAccuracy doubleValue] 
            course:[self.course doubleValue] 
            speed:[self.speed doubleValue] 
            timestamp:self.timestamp];

        [self setPrimitiveValue:location forKey:@"coreLocation"];

    }

    [self didAccessValueForKey:@"coreLocation"];

    return location;
}

The above allows the CLLocation object to be retrieved, and it will be refreshed when needed if there are changes to the dependent properties.

Conclusion

Transient properties are a very convenient way of extending subclasses of NSManagedObject. It’s a shame the documentation seems to be limited in scope and potentially inaccurate. Hopefully others will find this article helpful in understanding how to implement transient properties and make use of them in their own projects.



7 thoughts on “Core Data: Transient Properties on NSManagedObject”

  1. i’ve been trying to grok the whole transient / transformable situation for a couple days, and your notes have helped clarify. i agree that the apple documentation leaves a great deal to be desired.

    I have a custom class that, like CLLocation in your example, wants to be part of a larger NSManagedObject entity. I’m thinking I’d like to use a transient or transformable attribute, but I’d like to figure out how those work and have a better idea of the implications of the choice before making it. this whole area would benefit from more examples like yours. thanks at least for taking a completely opaque view and making it streaky gray :)

    1. Susan, thanks for the kind words, glad it helped as it had stumped me for a while.

      I’ve yet to dig in deeper, but I don’t think the transformable bit is necessary – I suspect in my example that the CLLocation is being converted to NSData and carried around in memory like that which is an unnecessary overhead. I’m hoping that Core Data realises that is not necessary when the transient option is on.

      The reason in my case for choosing transformable was because it gives the property an ‘id’ type. Leave that off and the class generator doesn’t specify a type, which is a bit broken! The more I work with Core Data I realise that the generation of the class files is a bit unnecessary. As you get familiar with what it outputs, you realise you could hand code it and avoid some of the frustration.

  2. Thanks so much for taking the time to write this blog post. Really informative – and much better than Apple’s docs – as you noted.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>