Could someone help me understand the primitive accessors with this example : i don't understand what is automatically set and the order of those methods :
1.after a person is created, is willSave
the first method called? (i guess so, because save:
is called after we create a person with insertNewObjectForEntityForName
)
2.in RootViewController (the second chunk of code), we then call the getter of eyeColor
with : person.eyeColor
:
a) in eyeColor
, we call : [self eyeColorData]
,
b) but setPrimitiveEyeColorData
is in willSave
, which is accessible only if primitiveEyeColor
exists,
c) but setPrimitiveEyeColor
is in eyeColor
and only called if [self eyeColorData]
exists. So, i'm a bit confused with this code, could someone help me?
here's the code about eyeColor
and eyeColorData
:
@dynamic eyeColorData;
@dynamic eyeColor;
@interface AWPerson (PrimitiveAccessors)
- (UIColor *)primitiveEyeColor;
- (void)setPrimitiveEyeColor:(UIColor *)value;
- (NSData *)primitiveEyeColorData;
- (void)setPrimitiveEyeColorData:(NSData *)value;
@end
+ (id)personInManagedObjectContext:(NSManagedObjectContext *)moc {
return [NSEntityDescription
insertNewObjectForEntityForName:@"Person"
inManagedObjectContext:moc];
}
+ (id)randomPersonInManagedObjectContext:(NSManagedObjectContext *)moc {
AWPerson *randomPerson = [self personInManagedObjectContext:moc];
//...
randomPerson.eyeColor = [self randomColor]; //setter eyeColor
return randomPerson;
}
+ (UIColor *)randomColor {
static NSArray *colorsArray = nil;
if( !colorsArray ) {
colorsArray = [[NSArray alloc] initWithObjects:
[UIColor lightGrayColor],
[UIColor blueColor],
[UIColor greenColor], nil];
}
int randomIndex = arc4random() % [colorsArray count];
return [colorsArray objectAtIndex:randomIndex];
}
- (void)willSave {
UIColor *color = [self primitiveEyeColor];
if( color ) {
[self setPrimitiveEyeColorData:
[NSKeyedArchiver archivedDataWithRootObject:color]];
} else {
[self setPrimitiveEyeColorData:nil];
}
[super willSave];
}
- (UIColor *)eyeColor {
[self willAccessValueForKey:@"eyeColor"];
UIColor *tmpValue = [self primitiveEyeColor];
[self didAccessValueForKey:@"eyeColor"];
if( tmpValue ) return tmpValue;
NSData *colorData = [self eyeColorData];
if( !colorData ) return nil;
tmpValue = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
[self setPrimitiveEyeColor:tmpValue];
return tmpValue;
}
in RootViewController :
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
AWPerson *person = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[cell setBackgroundColor:person.eyeColor];
}
Thanks
EDIT - Added info on willSave
To answer your first question, willSave is called whenever the object is saved (using the save
method). So the first method called will be one of the class methods (used to create the object) or init and then, since you said that the object is saved just after it is created, willSave gets called.
I think the key to understanding this is to realize that eyeColor
, primitiveEyeColor
, and their setters are all ultimately interacting with the same variable in memory (the iVar named eyeColor). The difference is whether or not the code in the setter/getter (in this case the - (UIColor *)eyeColor {
function) is called.
There are just a few different ways to interact with it:
[self primitiveEyeColor];
- This reads the value of the iVar directly.[self setPrimitiveEyeColor:tmpValue];
- This sets the value of the iVar directly.[self eyeColor]
- This calls the - (UIColor *)eyeColor
method in your class (which should ultimately retrieve the iVar or a representation of it).[self setEyeColor:value]
- This calls the - (void)setEyeColor:(UIColor *)newColor
method in your class. Note that in this case it doesn't exist so it simply calls the primitive method (and does the KVO magic).In this particular code, they are using a "non-standard persistent attribute" because NSManagedObject does not support UIColor's. Read about it here.
EDIT 2
To answer your other questions:
a) The color in
randomPerson.eyeColor = [self randomColor]
is accessible with[self primitiveEyeColor]
(inwillSave
)?
Yes, once eyeColor is set (either via the setEyeColor
method or the setPrimitiveEyeColor
method), you can read it from primitiveEyeColor
and it will return the same value.
Note that once it is set, eyeColor
and primitiveEyeColor
return the same value and can be called from anywhere in your class (not just willSave
).
b) So if
[self primitiveEyeColor] != nil
: ineyeColor
, the line :if( tmpValue ) return tmpValue;
should therefore always be true... when can we unarchiveeyeColorData
ifUIColor *tmpValue = [self primitiveEyeColor]
is always returned in-(UIColor *)eyeColor
?
This method only looks at eyeColorData
(which was stored during the last call to willSave
) if eyeColor
is nil. This is an optimization because we could skip all of this and just unarchive eyeColorData
every time if we wanted to. In this case, once a value is unarchived or set to a new value, it always stores that value and returns it so that we don't have to call unarchive again.
Also, there is really what I believe to be an error here (although it could be by design). Let's say that we perform the following steps:
eyeColor
to a random color (let's say blue).eyeColor
to nilNow, if you check the color using [self eyeColor]
it will see that primitiveEyeColor is nil
and unarchive eyeColorData
again, therefore returning the blue color that was stored previously. You should probably be over-riding the set function so that it sets eyeColorData
to nil
when eyeColor
is set to nil
. That way, checking the value of eyeColor
after setting it to nil
will return nil
as expected.
eyeColor
to nil after we save the object - Paul 2012-04-06 11:09
eyeColorData
: if primitiveEyeColor
does exist, it returns the value of type UIColor in - (UIColor *)eyeColor
, and we don't unarchive the data from colorData
... afterwards, i get what you say : "so that we don't have to call unarchive again", but what about the first time - (UIColor *)eyeColor {
is called? it seems to me that we stop at if( tmpValue ) return tmpValue;
because [self primitiveEyeColor]
returns the value from ...= [self randomColor]
Paul 2012-04-06 14:47
eyeColor
will be nil
so it sets tmpValue
to nil
. Then the line if (tmpValue) return tmpValue;
means "if tmpValue
is not nil
return that value otherwise continue" - lnafziger 2012-04-06 14:54
randomPerson.eyeColor = [self randomColor];
, then 2.-(void)willSave { UIColor *color = [self primitiveEyeColor];
returns the color set in 1), then in RootVC 3.[cell setBackgroundColor:person.eyeColor];
, then if( tmpValue ) return tmpValue;
? in this order, tmpValue
always contains a value, isn't it? Thanks again for your help, i really appreciat - Paul 2012-04-06 15:04
randomPerson.eyeColor = [self randomColor]
is accessible with[self primitiveEyeColor]
(inwillSave
)? b) so if[self primitiveEyeColor] != nil
: ineyeColor
, the line :if( tmpValue ) return tmpValue;
should therefore always be true... when can we unarchiveeyeColorData
ifUIColor *tmpValue = [self primitiveEyeColor]
is always returned in-(UIColor *)eyeColor
? thanks agai - Paul 2012-04-06 00:01