Search code examples
core-dataioskey-value-observingkey-value-coding

What's the point of -primitiveValueForKey:?


-setPrimitiveValue:forKey: won't trigger KVO notifications. But in my brain, KVO only makes sense when something changes. But how can change something when I only access it for read?

-primitiveValueForKey: only gets the object for some key. But it won't modify it. So why would/could this cause KVO notifications when using -valueForKey:?

(Of course there is a point, but I don't see it yet.)


Solution

  • The -primitiveValueForKey: and -setPrimitiveValue:forKey: methods are primarily used by Core Data. In particular, Core Data doesn't just need to know when you're going to modify an attribute; it needs to know when you're going to access it as well, so it can implement faulting.

    Thus Core Data adds -{will,did}AccessValueForKey: methods to use in getters, just as -{will,did}ChangeValueForKey: methods exist for use in setters to act as KVO hooks.

    However, there's another wrinkle: Core Data actually manages the underlying storage of modeled properties for you as well. So you need some way to manipulate this underlying storage within the barriers established by the -{will,did}{Access,Change}ValueForKey: methods. That's where -primitiveValueForKey: and -setPrimitiveValue:forKey: come in.

    This is why the standard pattern for implementing Core Data getters and setters, prior to the existence of @property and @dynamic, looked like this:

    // Person.m
    #import "Person.h"
    
    @implementation Person
    
    - (NSString *)name {
        [self willAccessValueForKey:@"name"];
        NSString *value = [self primitiveValueForKey:@"name"];
        [self didAccessValueForKey:@"name"];
    }
    
    - (void)setName:(NSString *)value {
        [self willChangeValueForKey:@"name"];
        [self setPrimitiveValue:value forKey:@"name"];
        [self didChangeValueForKey:@"name"];
    }
    
    @end
    

    Now of course, you can just declare a property and define it as @dynamic if you want Core Data to generate this stuff for you at runtime:

    // Person.h
    @interface Person : NSManagedObject
    @property (nonatomic, readwrite, copy) NSString *name;
    @end
    
    // Person.m
    #import "Person.h"
    
    @implementation Person
    @dynamic name;
    @end
    

    There are some situations though where you still want to manipulate the underlying storage without KVO or fault-firing, however. Thus Core Data provides a new way to get at this as well, also built around property declarations and automatic synthesis:

    // Person.h
    @interface Person : NSManagedObject
    @property (nonatomic, readwrite, copy) NSString *name;
    @end
    
    // Person.m
    #import "Person.h"
    
    @interface Person ()
    @property (nonatomic, readwrite, copy) NSString *primitiveName;
    @end
    
    @implementation Person
    @dynamic name;
    @dynamic primitiveName;
    @end
    

    Note that I put the class continuation in the .m file; it's not something that code outside Person (or even really code outside -awakeFromInsert and -awakeFromFetch) should touch. But it does let me get at the underlying storage for the "name" property without embedding literal strings in my code, and with the use of the real types.