Cocoa KVC Case Insensitivity?

I would appreciate some feedback on the specific approach I'm going to use. The following is a script.

I have an object (you can call it MObject), which has a number of properties, say, x and y coordinates, height and width. Properties are named according to KVC guidelines (MObject.x; MObject.height, etc.). My next task is to read in the XML file that this MObject describes. Unfortunately, XML elements are called differently - X and Y, Height and Width (note the capital letters).

Ideally, the XML elements will match the properties of the MObject. In this case, I could use KVC and avoid the whole code:

for (xmlProperty in xmlElement) { [MObject setValue:xmlProperty.value forKey:xmlProperty.name]. } 

One way to approach this would be to use case-insensitive keys. Where to begin? Are there any other better solutions?

Suggestions are greatly appreciated.

+6
design cocoa key-value-observing key-value-coding
source share
5 answers

Do not override -[NSObject valueForKey:] and -[NSObject setValue:forKey:] if you can help him at all.

It would be best to convert the keys that you get from the XML file on the fly. Use a separate method for the conversion, and you can also save a name cache for property keys, so you only need to do one conversion at a time.

 - (NSString *)keyForName:(NSString *)name { // _nameToKeyCache is an NSMutableDictionary that caches the key // generated for a given name so it only generated once per name NSString *key = [_nameToKeyCache objectForKey:name]; if (key == nil) { // ...generate key... [_nameToKeyCache setObject:key forKey:name]; } return key; } - (void)foo:xmlElement { for (xmlProperty in xmlElement) { [myObject setValue:xmlProperty.value forKey:[self keyForName:xmlProperty.name]]. } } 
+3
source share

You can use NSString lowercaseString to convert the XML key name to lowercase if that helps.

+3
source share

Override -valueForUndefinedKey: and -setValue:forUndefinedKey:

If you find a key with a different capital letter, use it, otherwise call super .

+3
source share

Override -valueForKey: and -setValue:forKey:

You should probably only accept the keys (element / attribute names) that you recognize, and call up to super for the other keys.

+1
source share

So, I implemented Chris Hanson’s proposal, and that’s what I ended up with. I put this in my Utils class. It stores a dictionary for each class that we are viewing. He could probably use a little refactoring, but so far he has worked very well.

 static NSMutableDictionary *keyCache; + (NSString *)keyForClass:(Class)klass column:(NSString *)column { if (!keyCache) { keyCache = [NSMutableDictionary dictionary]; } NSString *className = NSStringFromClass(klass); NSMutableDictionary *tableKeyCache = [keyCache objectForKey:className]; if (!tableKeyCache) { tableKeyCache = [NSMutableDictionary dictionary]; unsigned int numMethods = 0; Method *methods = class_copyMethodList(klass, &numMethods); NSMutableArray * selectors = [NSMutableArray array]; for (int i = 0; i < numMethods; ++i) { SEL selector = method_getName(methods[i]); [selectors addObject:NSStringFromSelector(selector)]; } [tableKeyCache setValue:selectors forKey:@"allSelectors"]; free(methods); [keyCache setValue:tableKeyCache forKey:className]; } NSString *keyToReturn = [tableKeyCache valueForKey:column]; if (!keyToReturn) { for (NSString *columnKey in [tableKeyCache valueForKey:@"allSelectors"]) { if ( [column caseInsensitiveCompare:columnKey] == NSOrderedSame) { [tableKeyCache setValue:columnKey forKey:column]; keyToReturn = columnKey; break; } } } if (!keyToReturn) { // Taking a guess here... NSLog(@"Selector not found for %@: %@ ", className, column); keyToReturn = [Utils keyForClass:[klass superclass] column:column]; } return keyToReturn; } 
+1
source share

All Articles