- Key-Value Coding
- The Model
- The Implementation
- What If
- Conclusion
The Implementation
To demonstrate how this will work, let us first create an XCode project. The project type should be "Core Data Document-based Application." Feel free to name this project anything you want; in this example, I named the project ParameterExample. At this point, we will add an NSManagedObjectContext to the document. For this example, I named the pointer managedObjectContext. At this point, any method in the document can access the context by calling [self managedObjectContext].
To facilitate the creation, reading, and writing of parameters, I created two helper methods for accessing the parameters. The first method retrieves parameters:
- (NSManagedObject *)findParameter:(NSString *)name { NSFetchRequest *request = [[NSFetchRequest alloc] init]; [request setEntity:[NSEntityDescription entityForName:@"Parameter" inManagedObjectContext:[[AppDelegate shared] managedObjectContext]]]; [request setPredicate:[NSPredicate predicateWithFormat:@"name == %@", name]]; NSError *error; NSArray *result = [[[AppDelegate shared] managedObjectContext] executeFetchRequest:request error:&error]; NSAssert(result != nil, ([NSString stringWithFormat:@"Error getting parameter %@:%@", name, error])); if ([result count] == 0) return nil; return [result objectAtIndex:0]; }
In this method, I created an NSFetchRequest and searched for the parameter based on its name. If there is no parameter by that name, I returned a nil value.
The second method will create a parameter:
- (NSManagedObject *)createParameter:(NSString *)name { id parameter = [NSEntityDescription insertNewObjectForEntityForName:@"Parameter" inManagedObjectContext:[[AppDelegate shared] managedObjectContext]]; [parameter setValue:name forKey:@"name"]; [[self mutableSetValueForKey:@"parameters"] addObject:parameter]; return parameter; }
In this method, I create a new parameter NSManagedObject and set its name value to the name passed in. I then return the newly created NSManagedObject.
With these methods in place, I can now add the two methods that perform the magic. First, I need to handle any requests for a parameter by its name:
- (id)valueForUndefinedKey:(NSString *)key { return [[self findParameter:key] valueForKey:@"value"]; }
In this very straightforward method, I attempt to receive a parameter based on the name passed in and immediately call valueForKey: on the result. If the parameter does not exist, I will be calling valueForKey: on a nil, which always returns nil and thus returns nil to the calling method.
The second part of this is to handle a value being set:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { id parameter = [self findParameter:key]; if (parameter == nil) parameter = [self createParameter:key]; if ([value isKindOfClass:[NSNumber class]]) { [parameter setValue:[value stringValue] forKey:@"value"]; } else { [parameter setValue:value forKey:@"value"]; } }
In this method, I first attempt to receive an existing parameter based on the name passed in. If the parameter does not yet exist, I create the parameter and then set the value. Note that in this method I am testing to see whether the value being passed in is an NSNumber. If it is, I call stringValue on the passed-in value to convert it to a string before setting it into the parameter. As a minor side reference, in Objective-C a BOOL would be passed into this method as an NSNumber. However, to turn it back into a BOOL requires a bit of trickery. Normally I solve this by adding a category to NSString that allows it to respond to boolValue just as an NSNumber does. This allows a BOOL to be stored and retrieved in a parameter completely transparent to the calling code.
And that is it. Four simple methods utilizing the existing APIs allow your document (or any Core Data application) to save parameters transparently. These parameters can be accessed programmatically or through bindings in InterfaceBuilder.