The summary of KVC

In iOS development, you can use setValue:forKey: to set the value of an object’s member variable. How does this work?

At the point where the setValue:forKey: method is called, right-click Jump to Definition

NSObject,NSArray,NSMutableDictionary,NSOrderedSet,NSSetYou can use itsetValue:forKey:Method, enter the first item to view

This method is defined in the nsKeyValuecoding.h file of the Foundation framework

And you can see from this that this is rightNSObjectAdded a category to make it usableKVCAbility. So did several others: I just see it hereNSKeyValueCodingHeader file, no underlying implementation seen. Check out the official Apple documentationKVCIn this paper.

Key-value coding (KVC) provides a mechanism for an object to access its attributes (member variables) indirectly by adding categories to the object.

Several types of KVC

General type

Person *p = [Person new];
[p setValue:@"Jake" forKey:@"name"];
NSLog(@ "% @",p.name);
Copy the code

Collection types

 Person *p = [Person new];
 NSArray *array = @[@ "1".@ "2".@ "3"];
 p setValue:array forKey:@"array"];
 NSLog(@ "% @",[p valueForKey:@"array"]);

 NSMutableArray *mArray = [p mutableArrayValueForKey:@"array"];
 mArray[0] = @ "100";
 NSLog(@ "% @",[p valueForKey:@"array"]);
Copy the code

Aggregate operator

  • @avgAverage:
  • @countNumber:
  • @maxThe maximum value of:
  • @min: the minimum
  • @sumThe sum:
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
    Person *p = [Person new];
    NSDictionary* dict = @{
                           @"length": @ (175 + arc4random_uniform(5)),}; [p setValuesForKeysWithDictionary:dict]; [personArray addObject:p]; }// The message is passed from array to string
NSLog(Array contents: %@, [personArray valueForKey:@"length"]);

float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
NSLog(@" Average value: %f", avg);

int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
NSLog(@" Quantity: %d", count);

int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
NSLog(@" Sum: %d", sum);

int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
NSLog(@" Max: %d", max);

int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
NSLog(@" Minimum value: %d", min);
Copy the code

Access non-object properties

typedef struct {
    float x, y, z;
} ThreeFloats;

Person *p = [Person new];
ThreeFloats floats = {1..2..3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[p setValue:value forKey:@"threeFloats"];
NSValue *threeFloatsValue  = [p valueForKey:@"threeFloats"];
NSLog(@ "% @",threeFloatsValue);

ThreeFloats th;
[threeFloatsValue getValue:&th];
NSLog(@"%f-%f-%f",th.x,th.y,th.z);
Copy the code

KeyPath access

Person *p = [Person new];
Job*job = [Job new];
p.job = job;
job.title = @ "development";
[p setValue:@ "code" forKeyPath:@"job.content"];
NSLog(@ % @ - % @ "",[p valueForKeyPath:@"job.title"],[p valueForKeyPath:@"job.content"]);
Copy the code

KVC process

Set the value process

Compare the process of apple official documents, let’s verify the actual operation:

  1. If that happensset<Key>:or_set<Key>I can set the value,set<Key>:The priority of is greater than_set<Key>

2. If all else fails,accessInstanceVariablesDirectlyThis method returnsYES, the instance variables are found in this order:_<key>._is<Key>.<key>.is<Key>To set the value 3. If the above methods and variables are not implemented, callsetValue: forUndefinedKey:, and throw an exception, implement this method can intercept exceptions, prevent crashes.

Value of the process

1. According to theget<Key>.<key>.is<Key>._<key>Return the value if it is found. 2. If none of the preceding methods can be found, a search is performedcountOf<Key>objectIn<Key>AtIndex:(or<key>AtIndexes:), which, if implemented, returns an array 3. If you haven’t found it yet, lookcountOf<Key>.enumeratorOf<Key>.memberOf<Key>:And return aNSSetobject4. If all else fails,accessInstanceVariablesDirectlyThis method returnsYES, the instance variables are found in this order:_<key>._is<Key>.<key>.is<Key>, complete the value 5. If the value is setvalueIf it is an object pointer, return it directly, if it is not, if it is supportedNSNumber, just convert it toNSNumberType return, not supportedNSNumberIs converted toNSValueThe object returned

6. If none is found, it will be executedvalueForUndefinedKey:If not implemented, this method will crash to throw an exception. Implementing this method can intercept exceptions and prevent crashes.

Custom KVC

Define the calling method:

@interface NSObject (KVC)- (void)mw_setValue:(id)value forKey:(NSString*)key; - (id)mw_valueForKey:(NSString *)key;
@end
Copy the code

Implementation:

#import "NSObject+KVC.h"
#import <objc/runtime.h>
@implementation NSObject (KVC)- (void)mw_setValue:(id)value forKey:(NSString *)key
{
    if (key == nil || key.length == 0) {
        return;
    }
    Set 
      
       ,_set< key>
      
    NSString *Key = key.capitalizedString;
    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    if ([self mw_performSelectorWithMethodName:setKey value:value]) {
        return;
    }else if ([self mw_performSelectorWithMethodName:_setKey value:value]) {
        return;
    }else if ([self mw_performSelectorWithMethodName:setIsKey value:value]) {
        return;
    }
    / / 2. Judge accessInstanceVariablesDirectly return values
    //2.1 Call stValue: forUndefinedKey:
    if(! [self.class accessInstanceVariablesDirectly] ) {
        [self setValue:value forUndefinedKey:key];
        return;
    }
    _is< key>, 
      
       ,is< key>
      
    NSMutableArray *mArray = [self getIvarListName];
    NSString *_key = [NSString stringWithFormat:@ "_ % @",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        // Get the corresponding IVAR
       Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        // Set the corresponding ivAR value
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:_isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:key]) {
       Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }
    // If no related instance is found, call stValue: forUndefinedKey:
    [selfsetValue:value forUndefinedKey:key]; } - (id)mw_valueForKey:(NSString *)key
{
    if (key == nil  || key.length == 0) {
        return nil;
    }
    1. Find the method get
       
       
         countOf
        
          objectIn
         
          AtIndex
         
        
       
      
    NSString *Key = key.capitalizedString;
    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
   
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
        if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
            int num = (int) [self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
            for (int i = 0; i<num- 1; i++) {
                num = (int) [self performSelector:NSSelectorFromString(countOfKey)];
            }
            for (int j = 0; j<num; j++) {
                id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
                [mArray addObject:objc];
            }
            returnmArray; }}#pragma clang diagnostic pop
    / / 2. Determine whether can direct assignment accessInstanceVariablesDirectly instance variables
    / / return NO 2.1
    if(! [self.class accessInstanceVariablesDirectly] ) {
        [self valueForUndefinedKey:key];
        return @ "";
    }
    
      
        _is< key> 
       
         is< key>
       
      
    NSMutableArray *mArray = [self getIvarListName];
    NSString *_key = [NSString stringWithFormat:@ "_ % @",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }
    //4. The valueForUndefinedKey call was not found
    [self valueForUndefinedKey:key];
    return @ "";
}
#pragmaMark - Related methods
- (BOOL)mw_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

- (id)performSelectorWithMethodName:(NSString *)methodName{
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
    }
    return nil;
}
- (NSMutableArray *)getIvarListName{
    
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
        NSLog(@"ivarName == %@",ivarName);
        [mArray addObject:ivarName];
    }
    free(ivars);
    return mArray;
}
@end
Copy the code

The flow chart of KVC