Search code examples
objective-ccore-dataprimitiveaccessor

primitive accessors in this example


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


Solution

  • 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:

    1. [self primitiveEyeColor]; - This reads the value of the iVar directly.
    2. [self setPrimitiveEyeColor:tmpValue]; - This sets the value of the iVar directly.
    3. [self eyeColor] - This calls the - (UIColor *)eyeColor method in your class (which should ultimately retrieve the iVar or a representation of it).
    4. [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] (in willSave)?

    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 : in eyeColor, the line : if( tmpValue ) return tmpValue; should therefore always be true... when can we unarchive eyeColorData if UIColor *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:

    1. Set eyeColor to a random color (let's say blue).
    2. save the object.
    3. Set eyeColor to nil

    Now, 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.