I started working with transient properties a little while ago, and thought I understood them pretty well, but this one isn't behaving how I thought it would. It is defined in the data model as a transient property with undefined type, and is declared as a property thus:
@property (nonatomic, readonly) NSSet * manufacturers;
This property is declared to be dependent on another 1:M relationship on this object:
+ (NSSet *) keyPathsForValuesAffectingManufacturers
{
return [NSSet setWithObject:@"lines"];
}
I have implemented a fairly standard getter for this transient property:
- (NSSet *) manufacturers
{
[self willAccessValueForKey:@"manufacturers"];
NSSet *mfrs = [self primitiveManufacturers];
[self didAccessValueForKey:@"manufacturers"];
if (mfrs == nil)
{
NSMutableSet *working = [NSMutableSet set];
/* rebuild the set of manufacturers into 'working' here */
mfrs = working;
[self setPrimitiveManufacturers:mfrs];
}
return mfrs;
}
The part that isn't working is that the cached primitive value for manufacturers
doesn't appear to be wiped out when objects are added / removed from the lines
relationship on this object. The manufacturers
accessor is definitely called when you add / remove lines, but [self primitiveManufacturers]
still returns the old cached value (which is obviously non-nil), so the code to rebuild the current set of manufacturers isn't called and I end up with an out-of-date return value.
Am I misunderstanding something about how cached transient property values are supposed to work? Or am I supposed to manually nullify the cached value when lines are changed through some kind of KVO callback so that the update code inside the "if" is executed the next time manufacturers
is accessed?
Any help much appreciated.
Edit: I am aware that I'm basically simulating a 1:M relationship with this transient property (i.e. a transient relationship, for want of a better word), and I'm also aware that the documentation warns against using setPrimitiveValue:forKey:
for relationships:
You should typically use this method only to modify attributes (usually transient), not relationships. If you try to set a to-many relationship to a new NSMutableSet object, it will (eventually) fail. In the unusual event that you need to modify a relationship using this method, you first get the existing set using primitiveValueForKey: (ensure the method does not return nil), create a mutable copy, and then modify the copy
However, since I am not setting a primitive value on a real 1:M relationship, only my simulated transient one, I had assumed that this warning did not apply to my use case.
Edit #2: Since I am only trying to track when objects are added or removed from the lines
set (this is the only way the expected value of manufacturers
can change), as opposed to trying to track updates to attributes on the objects that are already in the lines
set, I don't think I should need anything as heavyweight as this: https://github.com/mbrugger/CoreDataDependentProperties
Solution: In light of the answer below, I implemented the following solution to blank out the manufacturers
set when lines
is updated. Can anyone see any issues with this, or suggest any more efficient ways of implementing it? It is important that this operation be fast because it is done in the main thread and will visibly delay UI updates that happen at the same time as the line
updates are done.
- (void) awakeFromFetch
{
[super awakeFromFetch];
[self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}
- (void) awakeFromInsert
{
[super awakeFromInsert];
[self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}
- (void) didTurnIntoFault
{
[self removeObserver:self forKeyPath:@"lines"];
[super didTurnIntoFault];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([object isEqual:self] && [keyPath isEqualToString:@"lines"])
{
[self setPrimitiveManufacturer:nil];
}
else
{
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
keyPathsForValuesAffectingManufacturers does not mean that the value of manufacturers will be deleted when lines is changed, it means that objects observing manufacturers should be notified when lines changes, because changing lines automatically changes manufacturers without triggering notifications. This is used when one property gets its value based on another. For your situation, you need to change manufacturers when you change lines.