Is it possible to use a wildcard in KVC?

前端 未结 2 833
星月不相逢
星月不相逢 2021-02-06 16:37

I\'m trying to use wildcard in KVC like this.

Is it possible?

Or Is there other ways to use a wildcard to indicate a member variable?

@interface          


        
相关标签:
2条回答
  • 2021-02-06 16:51

    I'll raise your wildcards by adding Regex, and by using categories:

    To read about how regex works with this, please read the NSRegularExpression Class Reference.

    Features:

    • Uses regex, for matching of a wide variety of keys
    • Uses a category that works on any instance
    • Caches key lists per class
    • Full KVC support (not just properties, but accessor methods & iVars too!)
    • Integrates flawlessly with current KVC methods (only uses the regex if the key wasn't found, improving performance)
    • Subclassing doesn't mess it up, like @JamesWebster's solution
    • Doesn't needlessly pollute the list of keys with NSObject's methods
    • Returns a NSDictionary of matched keys & values

    Cons:

    • Uses regex, which is slower and more complex to understand
    • Slow initial lookup for a class (must iterate through all methods & iVars)
    • Automatically overwrites the -valueForUndefinedKey: method, so it's possible that this could break some existing code (move it to it's own method to fix).
    • Currently doesn't support setting of values (by design, that's a whole other bag of cats).
    • Can have duplicate keyPaths in the result (not the biggest of issues, but stems from the fact that KVC matching is complex, and I have to implement all of the rules)
    • Uses NSRegularExpression, which is only available in iOS 4 and later (not the largest of issues).

    Version History:

    • 1.0: Initial Release

    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)[A-J].*"; // match (case insensitive) a key starting with A - J
    
            NSLog(@"%@", [obj valueForKey:regex]); // prints "{ aKey = value; isProperty = 1; }"
        }
    }
    
    0 讨论(0)
  • 2021-02-06 16:53

    Though I couldn't find a way to support wildcards using the syntax you were attempting. I found this roundabout method using the Objective-C runtime!

    First we get all of the properties of the class you'd like to use

    #import <objc/runtime.h>
    
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList([MyClass class], &outCount);
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:outCount];
    for (int i = 0; i < outCount; i++)
    {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName)
        {
            NSString *propertyName = [NSString stringWithUTF8String:propName];
            [array addObject:propertyName];
        }
    }
    free(properties);
    

    Then filter out the ones you actually want

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF ENDSWITH '1'"];
    [array filterUsingPredicate:predicate];
    

    Then actually use them

    for (NSString *key in array)
        NSLog(@"%@", [testClass valueForKey:key]);
    
    0 讨论(0)
提交回复
热议问题