Summary of basic principles of iOS
Key-value Coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects use to access their properties indirectly. A property can be accessed through a string key. This indirect access mechanism complements the direct access provided by instance variables and their associated accessor methods.
KVC related API
Commonly used method
There are four commonly used methods
- through
key
Set the value/value
- (nullable id)valueForKey:(NSString *) 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; // use KeyPath to set the value - (void)setValue:(nullable id)value forKeyPath:(NSString *) KeyPath;Copy the code
Other methods
/ / the default returns YES, said if not found the Set < Key > method, according to _key, _iskey, Key, iskey sequential search members, Set to NO don't search + (BOOL) accessInstanceVariablesDirectly; //KVC provides an API for verifying the correctness of attribute values, which can be used to check if the value of a set is correct, make a replacement value for an incorrect value, or 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 pass nil to Value during the SetValue method, this method is called - (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 - Associated API assignments encoded by KVC key values
LGPerson *person = [[LGPerson alloc] init]; Person. name = @" cjl_ha "; // 2, person setValue:@"CJL_ 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
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's either one of them
Setter method, thenSet the value of the property directly
(Main 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
-
】 【 the second step: if you don’t have the first step of three simple setter method, is to find whether 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 through setValue:forKey: method to set the attribute name of LGPerson’s object person 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
, then enter [Step 2]
- if
-
If the getter in step 1 is not found, KVC will look for countOf
and objectIn
AtIndex: and
AtIndexes:
- If you find
countOf <Key>
And one of the other two will create a response for allNSArray
methodsCollection proxy object
And returns the object, that isNSKeyValueArray
, it isNSArray
theA subclass
. The proxy object then converts all received NSArray messages toCountOf <Key>, objectIn<Key> AtIndex: and <Key> AtIndexes:
A combination of messages used to create a key-value encoding object. If the original object also implements an object namedGet the Key > : range:
, the proxy object will use this method as appropriate. (Note: method names are named according to KVC’s standard naming conventions, including method signatures.) - If not found, please continue to [step 3]
- If you find
-
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 InstanceVariablesDirectly 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 uses valueForKey: to set the attribute name of LGPerson’s object person as an example, as shown in the figure below
Custom KVC
Principle: Implement custom cjl_setValue:forKey: and cjl_valueForKey: methods by adding classified CJLKVC to NSObject, according to the search rules provided in the Official Apple documentation
@interface NSObject (CJLKVC) // set the value - (void)cjl_setValue:(nullable id)value forKey:(NSString *)key; // value - (nullable id)cjl_valueForKey:(NSString *)key; @endCopy 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 the value - (void) cjl_setValue: (nullable value forKey: (id) nsstrings *) key {/ / 1, determine the key whether there is an if (key = = nil | | key. The length = = 0) return; NSString * key = key.capitalizedString; // setXXX * key = key.capitalizedString; NSString *setKey = [NSString stringWithFormat:@"set%@:", key]; NSString *_setKey = [NSString stringWithFormat:@"_set%@:", Key]; NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", Key]; if ([self cjl_performSelectorWithMethodName:setKey value:value]) { NSLog(@"*************%@*************", setKey); return; }else if([self cjl_performSelectorWithMethodName:_setKey value:value]){ NSLog(@"*************%@*************", _setKey); return; }else if([self cjl_performSelectorWithMethodName:setIsKey value:value]){ NSLog(@"*************%@*************", setIsKey); return; } / / 3, determine whether response ` accessInstanceVariablesDirectly ` method, namely indirect access the instance variables, returns YES, continue to the next set value, if it is NO 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]; NSMutableArray *mArray = [self getIvarListName]; NSMutableArray = [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 Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String); Object_setIvar (self, ivar, value); // set 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; @throw [NSException exceptionWithName:@"CJLUnknownKeyException" 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) cjl_valueForKey: (nsstrings *) key {/ / 1, the judgment is not empty the if (key = = nil | | key. The length = = 0) {return nil. } // 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)]; } / / collection types else if ([self respondsToSelector: NSSelectorFromString (countOfKey)]) {if ([the 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, continue to the next set value, if it is NO 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 to instance variables in the following order: _<key>, _is< key>, <key>, is< key> // 4.1 Define 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 @""; return @""; }Copy the 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; // use KeyPath to set the value - (void)setValue:(nullable id)value forKeyPath:(NSString *) KeyPath;Copy the code
Refer to the following case
//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; Person setValue:@" hee hee "forKeyPath:@"student.name"]; NSLog(@"%@",[person valueForKeyPath:@"student.name"]); } return 0; } / / * * * * * * * * * * * * * printing result * * * * * * * * * * * * * 2020-10-27 09:55:08. 512833 + 0800 001 - KVC profile (58089-6301894) before the change: CJL 2020-10-27 09:55:08.512929+0800 001-KVC [58089:6301894Copy 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 model - (void) setValuesForKeysWithDictionary: (NSDictionary keyedValues < > nsstrings *, id *); / / model dictionary - (NSDictionary < > nsstrings *, id *) dictionaryWithValuesForKeys: (NSArray keys < > nsstrings * *);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 shown below.
If (void)transmitMsg{NSArray *arrStr = @[@" English ", @"franch", @" Chinese "]; if (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); }} / / * * * * * * * * printing result * * * * * * * * 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 The 2020-10-27 11:33:43. 378417 + 0800 CJLCustom [60035-6380757] 7Copy the code
The appendix
See Github-CustomKVC_KVO for the complete code for custom KVC