I will raise your wildcards by adding Regex and using categories:
To find out how the regular expression works, read the NSRegularExpression class reference .
Features:
- Uses a regex to match a wide range of keys
- Uses a category that works in any instance.
- Cache Lists for Each Class
- Full support for KVC (not only properties, but also access methods and iVars!)
- Integrates flawlessly with current KVC methods (only regular expression is used if the key is not found, which improves performance)
- Subclass won't mess it up like @JamesWebster solution
- Inevitably pollutes the key list with
NSObject methods - Returns NSDictionary of matching keys and values
Minuses:
- Uses a regex that is slower and harder to understand
- Slow initial lookup for a class (must go through all methods and iVars)
- Automatically overwrites the
-valueForUndefinedKey: method, so itβs possible that this might break existing code (move it to your own method to fix it). - Currently, setting values ββis not supported (by design, that all other bag of cats).
- May have a duplicate keyPaths as a result (not the biggest of the problems, but stems from the fact that KVC compliance is complex and I have to implement all the rules).
- Uses
NSRegularExpression , which is only available in iOS 4 and later (not the biggest of problems).
Version History:
So here is the code:
NSObject + KVCRegex.h:
// // NSObject+KVCRegex.h // TestProj // // Created by Richard Ross on 8/20/12. // Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved. // #import <Foundation/Foundation.h> @interface NSObject (KVCRegex) // custom implemenation -(id) valueForUndefinedKey:(NSString *)key; @end
NSObject + KVCRegex.m:
// // NSObject+KVCRegex.m // TestProj // // Created by Richard Ross on 8/20/12. // Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved. // #import "NSObject+KVCRegex.h" #import <objc/runtime.h> @implementation NSObject (KVCRegex) static NSSet *keyPathsForClass(Class cls) { NSMutableSet *keys = [NSMutableSet set]; do { if (cls == [NSObject class]) { // nothing good can come from trying to use KVC on NSObject methods break; } unsigned count = 0; Method *methods = class_copyMethodList(cls, &count); for (int i = 0; i < count; i++) { // make sure that the method returns a value const char *methodName = sel_getName(method_getName(methods[i])); char returnType[64]; method_getReturnType(methods[i], returnType, 64); if (strcmp(returnType, "v") == 0) continue; // make sure that the method takes no args (except for self & _cmd) if (method_getNumberOfArguments(methods[i]) == 2) { // add a duplicate entry for ones matching 'is' if (strstr(methodName, "is") == methodName) { char *newStr = strdup(methodName + 2); newStr[0] = tolower(newStr[0]); [keys addObject:[NSString stringWithUTF8String:newStr]]; free(newStr); } [keys addObject:[NSString stringWithUTF8String:methodName]]; } } free(methods); // now copy iVars count = 0; Ivar *ivars = class_copyIvarList(cls, &count); for (int i = 0; i < count; i++) { const char *ivarName = ivar_getName(ivars[i]); if (strstr(ivarName, "_") == ivarName) [keys addObject:[NSString stringWithUTF8String:ivarName + 1]]; // iVar name starting with _<key> [keys addObject:[NSString stringWithUTF8String:ivarName]]; } free(ivars); } while ((cls = [cls superclass])); return [NSSet setWithSet:keys]; } // returns a dictionary based on 'key' as a regex -(id) valueForUndefinedKey:(NSString *)key { // lookup for later use static NSMutableDictionary *keyClassPairs; if (!keyClassPairs) keyClassPairs = [NSMutableDictionary dictionary]; if (!keyClassPairs[[self class]]) { keyClassPairs[(id<NSCopying>)[self class]] = keyPathsForClass([self class]); } NSSet *keyPaths = keyClassPairs[[self class]]; // assume 'key' is a regex NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:key options:0 error:nil]; NSMutableArray *matches = [NSMutableArray array]; for (NSString *keyPath in keyPaths) { NSRange matchRange = [regex rangeOfFirstMatchInString:keyPath options:0 range:(NSRange) { 0, keyPath.length }]; if (matchRange.length == keyPath.length) { // we have a match [matches addObject:keyPath]; } } if (matches.count) return [self dictionaryWithValuesForKeys:matches]; else [NSException raise:NSUndefinedKeyException format:@"Could not find a key that matches the regex in %@", key]; return nil; } @end
Example:
@interface MyObject : NSObject { @public int normalIvar; id _underscoreIvar; } @property id someProp; @property BOOL isProperty; @property int nativeProp; -(void) notAKey; -(id) aKey; @end @implementation MyObject @synthesize someProp, isProperty, nativeProp; -(void) notAKey { NSLog(@"Not a key!"); } -(id) aKey { return @"Value"; } @end int main() { @autoreleasepool { MyObject *obj = [MyObject new]; obj.someProp = @"a property"; obj.nativeProp = 15; obj.isProperty = YES; obj->normalIvar = 172; obj->_underscoreIvar = @"Ivar"; NSString *regex = @"[a|s].*"; // match a key starting with 'a' or 's', then matching anything else after NSLog(@"%@", [obj valueForKey:regex]); // prints "{ aKey = 'Value', someProp = 'a property' }" regex = @"_.*"; // match a key starting with '_', and then match anything else after NSLog(@"%@", [obj valueForKey:regex]); // prints "{ _underscoreIvar = 'Ivar' }" regex = @".*"; // match any key declared for this object NSLog(@"%@", [obj valueForKey:regex]); // prints "{ "_underscoreIvar" = Ivar; aKey = Value; isProperty = 1; nativeProp = 15; normalIvar = 172; property = 1; someProp = "a property"; underscoreIvar = Ivar; }" regex = @"(?i)[AJ].*"; // match (case insensitive) a key starting with A - J NSLog(@"%@", [obj valueForKey:regex]); // prints "{ aKey = value; isProperty = 1; }" } }
source share