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`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/Reference/NSManagedObject.html) 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:
#!objectivec
@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](#nonstandard) 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.
#!objectivec
@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:
#!objectivec
-(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 `NSManagedObject`s 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.
#!objectivec
-(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.
#!objectivec
-(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
### 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](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdNSAttributes.html), but buried in this section under ‘[An Object Attribute](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdNSAttributes.html#//apple_ref/doc/uid/TP40001919-SW12)’ 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:
#!objectivec
@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.
#!objectivec
@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).
#!objectivec
-(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.
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 🙂
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.
Nit typo: in your 3rd code snippet
fullName
method should return the value.Thanks so much for taking the time to write this blog post. Really informative – and much better than Apple’s docs – as you noted.
You can use Transformable value for CLLocation, and other properties, as long as they conform to NSCoder protocol.
Apple has a great code example here: https://developer.apple.com/library/ios/samplecode/DateSectionTitles/Introduction/Intro.html in which a transient property is used without the need of a category and it also shows a great way to make custom section titles.
Any idea how to do this in Swift?
No sorry. Not done much playing with Swift so far.
I am using some Transient property on my NSManagedObject to pre-calculate things that only need to be calculated once when dependent property is updated but when I reset the value on Transient property it never gets to saved.
For example:
-(NSString)age {
// notify core data to access value
[self willAccessValueForKey:@”age”];
// get primitive value
NSString age = [self primitiveValueForKey:@”age”];
// is age already exist?
if ( age == nil ) {
// get age
age = [GlobalHelper convertToAgeStringWithUTCDateOfBirth:(NSString*)self.dateOfBirth];
// set primitive value
[self setPrimitiveValue:age forKey:@”age”];
}
// notify core data done access value
[self didAccessValueForKey:@”age”];
return age;
}
-(void)setDateOfBirth:(id)dateOfBirth {
// change value for key
[self willChangeValueForKey:@”dateOfBirth”];
[self willChangeValueForKey:@”age”];
// set primitive
[self setPrimitiveValue:dateOfBirth forKey:@”dateOfBirth”];
[self setPrimitiveValue:nil forKey:@”age”];
// did change value for key
[self didChangeValueForKey:@”age”];
[self didChangeValueForKey:@”dateOfBirth”];
}
So as you can see from above code, when I am sync this object and when “dateOfBirth” field is updated it try to set “age” to “nil” and when accessing “age” field and if it is “nil” then calculate the age and never calculate it again.
However the problem is when I am setting “age” field to “nil” in “setDateOfBirth” setting it never saved and when I am accessing age again “age” property still has the previous value but “dateOfBirth” is updated.
Any help remotely related to this topic would be very much appreciated, I am having big trouble about this…
Great stuff!! I was just looking into what’s the best way to implement transient properties, and this seems like it’s the one! Thank you for writing this.
you also need to implement keyPathsForValuesAffectingValueForKey
I would like my NSManagedObject subclass to conform the the MKAnnotation protocol. So to conform to the protocol I need to implement the coordinate property (as documented here https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKAnnotation_Protocol/). Since coordinate this is a C struct (CLLocation2DCoordinate), I probably have to use a transient attribute and store the coordinate as NSValue. Am I right? If not, what approach do I have to use. Thx a lot!
I gave the core location example a shot but unfortunately I couldn’t get core data to recognise there was a change to the coreLocation property when setting the latitude and longitude. That is, in the objects changed in context notification, coreLocation didn’t appear in the changedValues for the changedValuesForCurrentEvent in the object. I had to remove the setLatitude and setLongitude methods and only use setCoreLocation, then it worked correctly.
Hi Dave!
Soulfully from the heart to the soul for the article.
And what can you say about NSSet and KVO in NSManagedObject ???