Key-value Coding (KVC) is a mechanism enabled by the NSKeyValueCoding informal protocol that objects use to access their properties indirectly, that is, they can access an attribute through a string Key. This indirect access mechanism complements the direct access provided by instance variables and their associated accessor methods.
KVC commonly used API
- through
key
Set the value/value
// Use Key directly
- (nullable id)valueForKey:(NSString *)key;
// Set the value by Key
- (void)setValue:(nullable id)value forKey:(NSString *)key;
Copy the code
- through
keyPath
(i.e.routing
) Set the value/value
// The value can be KeyPath
- (nullable id)valueForKeyPath:(NSString *)keyPath;
// set the value to KeyPath
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
Copy the code
Other methods
If Set
is not found, members will be searched in the order _key, _iskey, Key, and iskey
+ (BOOL)accessInstanceVariablesDirectly;
It can be used to check that the set value is correct, to make a replacement value for an incorrect value, or to reject a new value and return the cause of the error.
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// This is the set operation API, there are a series of such apis, if the property is an NSMutableArray, then you can use this method to return.
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
// If the Key does not exist and KVC cannot find any fields or attributes related to the Key, this method is called. By default, an exception is thrown.
- (nullable id)valueForUndefinedKey:(NSString *)key;
// Same as the previous method, but this method is set.
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// If you set SetValue to nil for a basic data type such as' int 'or' float ', it triggers a call to this method and you can do exception handling or reassign keys in this method
- (void)setNilValueForKey:(NSString *)key;
// Input a set of keys, return the values corresponding to the keys, and then return the dictionary, which is used to transfer the Model to the dictionary.
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
Copy the code
KVC set value underlying principle
In daily development, there are two ways to assign object attributes
- Directly through
setter
Methods the assignment - through
KVC key value encoding
The related API assignment of
LGPerson *person = [[LGPerson alloc] init];
// 1. General setter methods
person.name = @"Lg_ ha ha";
// 2
[person setValue:@"Lg_ hee hee" forKey:@"name"];
Copy the code
The following is to explore the underlying principle of the most used KVC value setting method: setValue:forKey.
The setValue:forKey declaration is found in the Foundation framework, which is not open source. There are several ways to explore the underlying layer
- through
Hopper
Disassemble, look at pseudocode - By apple
The official documentation
Github
Search for relevant demos. For exampleDIS_KVC_KVO
Here, we studied the key-value Coding Programming Guide from apple’s official document, and the Value setting process is described as follows
When setValue:forKey: sets the property value, the underlying execution flow is as follows:
-
Set
-> _set
-> setIs >
- If there is one of them
Any setter method
, is called in this methodSet the value of the key
(note:Key is the name of a member variable
, the first character case must conform to KVC naming conventions. - If all
There is no
, then enter [Step 2]
- If there is one of them
-
】 【 the second step: if you don’t have the first step of three simple setter method, is to find whether + (BOOL) accessInstanceVariablesDirectly returns YES
-
If YES is returned, the indirectly accessed instance variables are assigned in the following order: _
-> _is< key>
- If any of the instance variables are found, the value is assigned
- If none, go to step 3.
-
If NO is returned, go to step 3.
-
-
If neither setter method nor instance variable is found, the system executes the setValue: forUndefinedKey: method of the object, throwing an exception of type NSUndefinedKeyException by default.
To sum up,KVC sets the value using setValue:forKey:
The process of setting the attribute name of the object person of LGPerson is taken as an example, as shown in the figure below:
Underlying principles of KVC value
Similarly, we can analyze the underlying principle of KVC value through official documents.
When valueForKey: is called, the underlying execution flow is as follows
-
Get
->
-> is
-> _< Key>
- if
find
, then enter [Step 5] - if
Could not find
, Array Set type goes to step 2, Set Set type goes to Step 3, and others go to Step 4.
- if
-
If the getter in step 1 is not found, KVC will look for countOf
and objectIn
AtIndex: and
AtIndexes:
-
If one of countOf
and the other two is found, a collection proxy object that responds to all NSArray methods is created and returned, NSKeyValueArray, which is a subclass of NSArray. The proxy object then converts all the NSArray messages it receives into some combination of countOf
, objectIn
AtIndex:, and
AtIndexes: messages to create key-value encoded objects. If the original object also implements an optional method called get
: range:, then the proxy object will use that method as appropriate (note: method names are named according to KVC’s standard naming conventions, including method signatures).
-
If not, proceed to step 4 if these three access the array.
-
-
If none of the above methods are found, the countOf
, enumeratorOf
, and memberOf
methods are searched simultaneously
- If all three methods are found, a response is created
A collection of proxy objects for all NSSet methods
And returns the object, which the proxy object then receives allNSSet
Message conversion toCountOf <Key>, enumeratorOf<Key> and memberOf<Key> :
Some combination of messages used to create its objects - If still not found, go to [Step 4]
- If all three methods are found, a response is created
-
If you haven’t found, the fourth step 】 【 check method accessInstanceVariablesDirectly YES, search in turn _ < key >, _is < key >, < the key > or is < key > instance variables
- If I find it,
Gets the value of the instance variable directly
, enter [Step 5]
- If I find it,
-
[Step 5] Return different results according to the type of attribute value searched
- If it is
Pointer to the object
, the result is returned directly - If it is
NSNumber support
The scalar type of theStored in an NSNumber instance
And return it - If is were
NSNumber does not support
Scalar type, pleaseTo an NSValue object
And returns the object
- If it is
-
[Step 6] If the above five methods fail, the system executes the valueForUndefinedKey: method of the object, which by default throws an exception of type NSUndefinedKeyException
To sum up, KVC value process through valueForKey: method takes setting the attribute name of LGPerson object person as an example, as shown in the figure below:
KVC assignment and value example source code
Use route access, known as keyPath
, in the daily development of a class member variables can be custom class or other complex data types, operation is commonly, we can get the first through the KVC attributes, and then through the KVC get custom attributes of a class, is more troublesome, there is another more simple method, is to use the KeyPath routing, involves the following two methods: SetValue: forKeyPath: and valueForKeyPath:
// The value can be KeyPath
- (nullable id)valueForKeyPath:(NSString *)keyPath;
// set the value to KeyPath
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
Copy the code
Consider the following examples:
/ / CJLPerson class
@interface CJLPerson : NSObject
@property (nonatomic, copy) NSString *age;
@property (nonatomic, strong) CJLStudent *student;
@end
/ / CJLStudent class
@interface CJLStudent : NSObject
@property (nonatomic, copy) NSString *name;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
CJLPerson *person = [[CJLPerson alloc] init];
CJLStudent *student = [CJLStudent alloc];
student.name = @"CJL";
person.student = student;
// Change the value of student's subject property based on the kvC-keypath route
[person setValue:@"Hee hee" forKeyPath:@"student.name"];
NSLog(@"% @",[person valueForKeyPath:@"student.name"]);
}
return 0;
}
//************* The result is printed *************
2020-10-27 09:55:08.512833+0800 001- introduction of KVC [58089:6301894[before: CJL2020-10-27 09:55:08.512929+0800 001- introduction of KVC [58089:6301894[after change: hee heeCopy the code
Custom KVC
Custom KVC source code
Implement custom lg_setValue:forKey: and lg_valueForKey: methods by adding the classification LGKVC to NSObject, according to the lookup rules provided in the Official Apple documentation
@interface NSObject (LGKVC)
/ / set value
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key;
/ / value
- (nullable id)lg_valueForKey:(NSString *)key;
@end
Copy the code
Custom KVC setting
The process for customizing KVC Settings is divided into the following steps:
-
1. Check whether key is null
-
2. Find setXXX, _setXXX, setIsXXX
-
3, determine whether response accessInstanceVariablesDirectly method, namely indirect access the instance variables,
- return
YES
, proceed to the next set value, - If it is
NO
Collapse,
- return
-
4. Assign values to indirect access variables (only once) in the following order: _key, _isKey, key, isKey
- 4.1 Define a mutable array to collect instance variables
- 4.2 through
class_getInstanceVariable
Method to obtain the corresponding IVAR - 4.3 through
object_setIvar
Method to set a value for the corresponding IVAR
-
5. If the related instance variable cannot be found, throw an exception
/ / set value
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// 1. Check whether key exists
if (key == nil || key.length == 0) return;
// 2, find setXXX, _setXXX, setIsXXX
// Use uppercase key
NSString *Key = key.capitalizedString;
// Use uppercase key
NSString *setKey = [NSString stringWithFormat:@"set%@:", Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:", Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", Key];
if ([self lg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"* * * * * * * * * * * * * % @ * * * * * * * * * * * * *", setKey);
return;
}else if([self lg_performSelectorWithMethodName:_setKey value:value]){
NSLog(@"* * * * * * * * * * * * * % @ * * * * * * * * * * * * *", _setKey);
return;
}else if([self lg_performSelectorWithMethodName:setIsKey value:value]){
NSLog(@"* * * * * * * * * * * * * % @ * * * * * * * * * * * * *", setIsKey);
return;
}
/ / 3, determine whether response ` accessInstanceVariablesDirectly ` method, namely indirect access the instance variables, returns YES, set values continue to the next step, if NO, the collapse
if(! [self.class accessInstanceVariablesDirectly]) { @throw [NSException exceptionWithName:@"CJLUnKnownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4. Assign values to indirect access variables in _key, _isKey, key, isKey order
// 4.1 Defines a mutable array to collect instance variables
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_ % @", key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@", key];
NSString *isKey = [NSString stringWithFormat:@"is%@", key];
if ([mArray containsObject:_key]) {
4.2 Obtaining the ivAR
Ivar ivar = class_getInstanceVariable([self class]._key.UTF8String);
// 4.3 Set the 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 not found, throw an exception
@throw [NSException exceptionWithName:@"lgUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
Copy the code
The KVC value is customized
The custom code for the value is as follows
-
1. Check whether key is null
-
2. Find methods in the order: Get
,
, countOf
, and objectIn
AtIndex
-
3, determine whether can direct assignment instance variables, which determine whether response accessInstanceVariablesDirectly method, indirect access the instance variables,
- return
YES
To proceed to the next step - If it is
NO
Collapse,
- return
-
_is< key> is< key>
- 4.1 Define a collection instance variable
An array variable
- 4.2 through
class_getInstanceVariable
Method to obtain the corresponding IVAR - 4.3 through
object_getIvar
Method to return the value of the corresponding IVar
- 4.1 Define a collection instance variable
/ / value
- (nullable id)lg_valueForKey:(NSString *)key{
// 1
if (key == nil || key.length == 0) {
return nil;
}
Get
countOf
objectIn
AtIndex
// Use uppercase key
NSString *Key = key.capitalizedString;
// Splice method
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)];
}
// Set type
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, determine whether response ` accessInstanceVariablesDirectly ` method, namely indirect access the instance variables, returns YES, set values continue to the next step, if NO, the collapse
if(! [self.class accessInstanceVariablesDirectly]) { @throw [NSException exceptionWithName:@"CJLUnKnownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
_is< key>, _is< key>, is< key>
// 4.1 Defines a mutable array to collect instance variables
NSMutableArray *mArray = [self getIvarListName];
For example, _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
KVC usage scenarios
1. Dynamically set values and values
- Common ones can pass
setValue:forKey:
和valueForKey:
- Also through
routing
The way ofsetValue:forKeyPath:
和valueForKeyPath:
2. Access and modify private variables through KVC
In daily development, private attributes of a class are not directly accessible to externally defined objects, but as far as KVC is concerned, an object has no privacy of its own, so any private attributes can be modified and accessed through KVC
3, multi-value operation (model and dictionary interturn)
Model and dictionary transformations can be implemented using the following two KVC apis
// dictionary to model
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
// Model to dictionary
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
Copy the code
Modify some internal properties of system space
Metagtext @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText @metagText
5. Use KVC to achieve higher-order messaging
When using KVC on a container class, valueForKey: is passed to every object in the container. Instead of operating on the container itself, the result is added to the returned container, making it easy to operate on the collection to return another collection
As follows:
//KVC implements higher-order messaging
- (void)transmitMsg{
NSArray *arrStr = @[@"english"The @"franch"The @"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); }}//******** The result is printed ********
2020-10-27 11:33:43.377672+0800 CJLCustom[60035:6380757] English
2020-10-27 11:33:43.377773+0800 CJLCustom[60035:6380757] Franch
2020-10-27 11:33:43.377860+0800 CJLCustom[60035:6380757] Chinese
2020-10-27 11:33:43.378233+0800 CJLCustom[60035:6380757] 7
2020-10-27 11:33:43.378327+0800 CJLCustom[60035:6380757] 6
2020-10-27 11:33:43.378417+0800 CJLCustom[60035:6380757] 7
Copy the code
reference
This article learns and references iOS- Basic Principle 22: KVC basic principle, thanks here