KVC is Apple’s key-value Coding technology, which stands for key-value Coding. Key-value encoding is a mechanism supported by the NSKeyValueCoding informal protocol that objects use to provide indirect access to their properties. KVC allows you to quickly get and set the value of a property. The address of the official document

NSObject complies with the NSKeyValueCoding protocol, and all objects that inherit from NSObject can call KVC methods.

@interface NSObject(NSKeyValueCoding)
...
@end
Copy the code

1. Use

1.1 Accessing Object Properties

1.1.1 Generally, setter methods are used to set properties. KVC provides a more flexible setting method. The following
[myAccount setCurrentBalance:@(100.0)]

[myAccount setValue:@(100.0) forKey:@"currentBalance"];
Copy the code
1.1.2 API for getting attributes by keys
➤valueForKey: searches for attributes in the same order. An error message is returned when valueForUndefinedKey is found. Ditto ➤ dictionaryWithValuesForKeys: through key (key) array to obtain the corresponding key/value dictionary, can be used as the object dictionary. For example, DWZPerson *p1 = [DWZPerson new]; p1.name = @"rose"; p1.hobby = @"play"; NSDictionary *d = [p1 dictionaryWithValuesForKeys:@[@"name",@"hobby"]]; The output is {hobby = play; name = rose; } ➤ Returns the values of all subsequent paths to the set if one of the values in the middle is a set when addressing keyPath. The followingCopy the code

1.1.3 API for setting properties by key
➤setValue:forKey: Sets the value if the key is correct. Is not correct, setValue: forUndefinedKey: ➤ setValue: forKeyPath: ➤ setValuesForKeysWithDictionary: turn model commonly used as a dictionary.Copy the code

1.2 Accessing collection properties

MutableArrayValueForKey: and mutableArrayValueForKeyPath: they return behavior is similar to the NSMutableArray object proxy objects. MutableSetValueForKey: and mutableSetValueForKeyPath: they return behavior is similar to the object NSMutableSet proxy objects. MutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath: they return behavior is similar to the object NSMutableOrderedSet proxy objects. Operate (add, delete, replace) proxy objects, and the implementation of the protocol modifies the underlying properties accordingly. It is more efficient than using valueForKey: to get a non-mutable collection object, create a modified object whose contents have changed, and then store it back into that object using setValue: forKey: messages.Copy the code

1.3 Using the collection operator

1.3.1 Group operators include: @avg,@count,@max,@min,@sum
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg. Amount "]; // numberOfTransactions NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"]; NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"]; NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"]; // Attribute amount sum NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];Copy the code
1.3.2 Array operators
Important: Using the array operator if the child object is nil causes the valueForKeyPath: method to fail. The array operators include: @distinctunionofObjects Returns a different set of objects (equivalent to a de-duplication operation) NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"]; @unionofObjects Returns all results NSArray *payees = [self.transactions valueForKeyPath:@" @unionofObjects.payee "];Copy the code
1.3.3 Nested operators: Operate on nested collections
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
@distinctUnionOfArrays
NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
@unionOfArrays
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
@distinctUnionOfSets
Copy the code

1.4 Performance of KVC in non-objects

KVC can operate on OC objects and basic data structures at the same time, and the implementation of KVC can automatically convert on the objects and basic data structures. If you set nil to a non-object property, the setNilValueForKey: method is called and an error is reported if the method is not overridden.

1.4.1 Basic data types are converted nsnumbers, such as BOOL, char, int, double, float, long, short
[p1 setValue:@(20) forKey:@"age"];
[p1 setValue:@(true) forKey:@"isfemale"];
BOOL v3 = [p1 valueForKey:@"isfemale"];
NSNumber *v4 = [p1 valueForKey:@"age"];
Copy the code
1.4.2 Structure types are all converted to NSValue, such as NSPoint, NSRange, NSRect, and NSSize

For a custom structure, use the following:

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

@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end

NSValue* result = [myClass valueForKey:@"threeFloats"];
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
Copy the code

1.5 Verifying Attributes

//KVC provides an API for verifying the correctness of attribute values. ValidateValue :(inout id _Nullable * _Nonnull)ioValue - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;Copy the code

2 KVC principle

2.1 Search order of get methods

Step 2 Check whether countOf<Key>, objectIn<Key>AtIndex:, <Key> AtIndexes: If it contains the first method and one of the last two methods, it determines that it is an array, creates an NSArray object and returns it; Otherwise go to step 3. 3 Check whether countOf <Key>, enumeratorOf <Key>, memberOf <Key> are included: Methods, including judgment is NSSet, create NSSet object and returns, otherwise, skip to step 4. 4 accessInstanceVariablesDirectly return value, to YES, Find instance variables _<key>, _is< key>, <key>, is< key> in order to find jump step 5; Otherwise, go to step 6. 5 If the value is an object pointer, the result is displayed. If it is a basic data type, cast to NSNumber and return; If it is not a basic data type, cast to NSValue and return. 6 If all methods fail, call valueForUndefinedKey: and throw an error if the method is not implemented on the class's inheritance chain.Copy the code

2.2 Method search order of set

1 set value order: set the < Key >, _set < Key >, setIs < Key >, find the method is invoked by the return value and return 2 accessInstanceVariablesDirectly judgment, to YES, instance variables and return a value; Is NO, go to step 4. 3 Set the order of instance variables: _<key>, _is< key>, <key>, is< key>, if there are corresponding instance variables set and return, if not found, go to step 4. 4 call setValue: forUndefinedKey: and throw an error if the inheritance chain does not implement the method.Copy the code

3 Customize KVO

3.1 Customize GET methods based on principles

/ / / / / / @ custom KVCGet method param key key - (nullable id) dwz_GETValueForKey: (nsstrings *) key {/ / 1: choose brush key judgment is not empty if (key = = nil | | key.length == 0) { return nil; } // 2: get<Key> <Key> countOf<Key> objectIn<Key>AtIndex 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]; } return mArray; Pragma clang Diagnostic pop // 3: pragma clang diagnostic pop // 3: pragma Clang diagnostic pop // 3: pragma Clang diagnostic pop // [self.class accessInstanceVariablesDirectly] ) { @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil]; NSMutableArray *mArray = [self getIvarListName]; // _<key> _is<Key> <key> is<Key> // _name -> _isName -> name -> isName 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);; } return @""; }Copy the code

3.2 Customize SET methods based on principles

/// customize KVC method // @param value key // @param key value -(void)dwz_SetValue:(NSString *)value forKey:(NSString *)key {//  1. Determine whether the key has a value if (key = = nil | | key. The length = = 0) {NSLog (@ "dwz_SetValue error with key is nil"); return; } NSString *k = key.capitalizedString; NSString *setKey = [NSString stringWithFormat:@"set%@:",k]; NSString *_setKey = [NSString stringWithFormat:@"_set%@:",k]; NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",k]; / / the setKey method calls the if ([self dwz_PerformSelectorWithMethodName: setKey value: the value]) {NSLog (@ - % @ "% s", __func__, setKey); return; } / / _setKey method calls the if ([self dwz_PerformSelectorWithMethodName: _setKey value: the value]) {NSLog (@ - % @ "% s", __func__, _setKey); return; } / / _setIsKey method calls the if ([self dwz_PerformSelectorWithMethodName: setIsKey value: the value]) {NSLog (@ % s. -%@",__func__,setIsKey); return; } // 3. Determine whether to allow direct access to the member variable if (! [[self class] accessInstanceVariablesDirectly]) { @throw [NSException exceptionWithName:@"DWZUnknownKeyException" reason:[NSString stringWithFormat:@"DWZ [%@ setValueForUndefineKey:]%@",self,key] userInfo:nil]; return; } NSMutableArray *ivars = [self getIvarListName]; NSString *_key = [NSString stringWithFormat:@"_%@",key]; NSString *_isKey = [NSString stringWithFormat:@"_is%@",k]; NSString *isKey = [NSString stringWithFormat:@"is%@",k]; if ([ivars containsObject:_key]) { Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String); object_setIvar(self, ivar, value); return; } if ([ivars containsObject:_isKey]) { Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String); object_setIvar(self, ivar, value); return; } if ([ivars containsObject:key]) { Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); object_setIvar(self, ivar, value); return; } if ([ivars containsObject:isKey]) { Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String); object_setIvar(self, ivar, value); return; } // This is the case, The correct setValueForUndefineKey: method @throw [NSException exceptionWithName:@"DWZUnknownKeyException" Reason :[NSString stringWithFormat:@"DWZ [%@ %@]%@",self,NSStringFromSelector(_cmd),key] userInfo:nil]; }Copy the code

4. Usage scenarios and Precautions

4.1 Mutual transfer between dictionary and model

/ / dictionary model - (void) setValuesForKeysWithDictionary: (NSDictionary keyedValues < > nsstrings *, id *); / / model dictionary - (NSDictionary < > nsstrings *, id *) dictionaryWithValuesForKeys: (NSArray keys < > nsstrings * *);Copy the code

4.2 Setting hidden Attribute Values

PlaceHolderAttributeText for UITextFieldCopy the code

4.3 Operate set Data

- (void)transmitMsg{ NSArray *arrStr = @[@"english", @"franch", @"chinese"]; NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"]; for (NSString *str in arrCapStr) { NSLog(@"%@", str); } NSArray *arrCapStrLength = [arrCapStr valueForKeyPath:@"capitalizedString.length"]; for (NSNumber *length in arrCapStrLength) { NSLog(@"%ld", (long)length.integerValue); }} 2021-04-27 17:50:47.982831+0800 KVC[1678:58128] English 2021-04-27 17:50:47.983082+0800 KVC[1678:58128] Franch 2021-04-27 17:50:47.983330+0800 KVC[1678:58128] Chinese 2021-04-27 17:50:47.985906+0800 KVC[1678:58128] 7 2021-04-27 17:50:47.986096+0800 KVC[1678:58128] 6 2021-04-27 17:50:47.986247+0800 KVC[1678:58128] 7Copy the code

4.3 Implement setValueForUndefineKey: and setNilValueForKey: methods to prevent the abnormal crash of set values.