preface
In daily development, we often use KVC to assign values or access private properties. So what is KVC and how does it work? Let’s explore and analyze it.
KVC profile
Key-value coding(KEY-value coding)
Is composed ofNSKeyValueCoding Informal protocol
Enabled mechanism that an object uses to provide indirect access to its properties.When an object conforms to key-value encoding, its properties can be addressed with string arguments through a concise, unified messaging interface. This indirect access mechanism complements the direct access provided by instance variables and their associated access methods.- To view
setValue:forKey:
Source code, finally toFoundatin
Framework of theNSKeyValueCoding
File:
However, Foundation is not open source, so you can only check out KVC’s official documentation: Key-Value Coding Programming Guide
Replication and value of KVC
First determine the location to see, according to the document foundAccessor Search Patterns
:
- First to see
setter
methods
Basic Setter
- The document says call
setValue:forKey:
When you assign a value to a property,-
- They’ll look in order
set<Key>
or_set<Key>
And if I find it, I assign it,
- They’ll look in order
-
- If it’s not found, if the class method
accessinstancevariablesdirect
The return value isYES
, will look for instance variables in order_<key>
._is<Key>
.<key>
Or,is<Key>
, and assign it a value
- If it’s not found, if the class method
-
- If I don’t find it, I’ll leave
setValue:forUndefinedKey:
methods
- If I don’t find it, I’ll leave
-
Code validation
-
- Let’s define one
LGPerson
Class and set instance variables to implementsetName
and_setName
Method, finally inViewController
In the callsetValue:forKey:
Methods:
- Let’s define one
// .h
@interface LGPerson : NSObject {
@public
NSString *_isName;
NSString *name;
NSString *isName;
NSString *_name;
}
// .m
- (void)setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
// ViewController.m
LGPerson *person = [[LGPerson alloc] init];
[person setValue:@"wushuang" forKey:@"name"];
Copy the code
The print result is as follows:
The setValue:forKey: method is called, and the setName method is commented out, leaving _setName, and then run:
This time we go to the _setName method, which means that when we call setValue:forKey:, we look for the setName method first, and if we don’t find it, we look for _setName.
- when
setName
and_setName
When they’re not implemented, they’re looking for instance variables_name
._isName
.name
Or,isName
If isName and _isName are added, then setIsName and _setIsName are added. Let’s verify:
- Comment the first two first
set
Method, and then addsetIsName
and_setIsName
Methods:
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)_setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
Copy the code
- Run the results found that go
setIsName
The method did not go_setIsName
, and you get the new process:
New process: after calling setValue:forKey: the lookup order is setName -> _setName -> setIsName
- when
set
If the associated method is not found, it will look for the associated instance variable and comment it out firstset
Related methods, and then implement the class methods firstaccessInstanceVariablesDirectly
, the return value isYES
To print the value of the instance variable:
// LGPerson.m
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
// ViewController.m
NSLog(@ "% @ - % @ - % @ - % @",person->_name,person->_isName,person->name,person->isName);
Copy the code
Running result:
- Will be find
_name
, comment out the first printed instance variables and then print them, and finally get the order in which the instance variables are printed:_name
->_isName
->name
->isName
- Comment out the four instance variables and implement the method
setValue:forUndefinedKey
The print:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"%s -- %@ --- %@", __func__, value, key);
}
Copy the code
Here are the results:
- Shown that when
Set method
andThe instance variables
When they don’t, they leavesetValue:forUndefinedKey
Methods.
The flow chart
Basic Setter
The process is as follows:
Basic Getter
Search Pattern for the Basic Getter:
- Which is to implement
valueForKey:
Method, the system takes the following steps (regardless of the collection type) :-
- Search the instance method for the first name
getName
,name
,isName
or_name
The accessor method of. If it is found, it is called
- Search the instance method for the first name
-
- If it is not found (remove the collection type), check the class method first
accessInstanceVariablesDirectly
Implementation and returnYES
And then search for instance variables in turn_name
._isName
.name
Or,isName
Gets the value of the instance variable and returns it
- If it is not found (remove the collection type), check the class method first
-
- If I don’t find it, I’ll leave
valueForUndefinedKey:
methods
- If I don’t find it, I’ll leave
-
Code validation
After commenting all the code associated with the set, implement the first step:
// LGPerson.m
- (NSString *)getName{
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
// ViewController.m
NSLog(@" Value: %@",[person valueForKey:@"name"]);
Copy the code
- Then, after printing one, comment out the printed one and run it to get the order of value of the instance methods in step 1:
getName
->name
->isName
->_name
Then comment out the instance method and assign the instance variable:
// ViewController.m
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@" Value: %@",[person valueForKey:@"name"]);
Copy the code
- And then I print out one,
annotation
Drop aAssignment to instance variables
andThe instance variables
, and then print to get the value order of the instance variables in the second step:_name
->_isName
->name
->isName
- Comment out the instance variable and its assignment, and then
LGPerson.m
implementationvalueForUndefinedKey:
Methods:
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"%s --- %@", __func__, key);
return NSStringFromSelector(_cmd);
}
// ViewController.m
NSLog(@" Value: %@",[person valueForKey:@"name"]);
Copy the code
- The running results are as follows:
So far, the three steps of the value process can be verified
The flow chart
Basic Setter
The search process is as follows:
Custom KVC
- To understand the
KVC
thesetter
andgetter
And then we can define it ourselvesKVC
? The answer is yes. - According to the
NSKeyValueCoding
In thesetter
andgetter
Method, in the definition ofNSOjbect
Category defines newsetter
andgetter
Method, and then defineWSPerson
And there areset
Instance methods and instance variables:
@interface NSObject (WSKVC)
- (void)ws_setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)ws_valueForKey:(NSString *)key;
@end
// WSPerson.h
@interface WSPerson : NSObject {
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
// WSPerson.m
@implementation WSPerson
+ (BOOL)accessInstanceVariablesDirectly {
return true;
}
- (void)setName:(NSString *)name {
NSLog(@"%s --- %@", __func__ ,name);
}
- (void)_setName:(NSString *)name {
NSLog(@"%s --- %@", __func__ ,name);
}
- (void)setIsName:(NSString *)name {
NSLog(@"%s --- %@", __func__ ,name);
}
@end
Copy the code
- The custom methods are then processed according to the value and assignment process analyzed earlier
The assignmentws_setValue:forKey:
The code is as follows:
- (void)ws_setValue:(nullable id)value forKey:(NSString *)key {
if (key == nil || key.length == 0) {
return;
}
NSString *Key = key.capitalizedString; // Capitalize the first letter
// Concatenate the associated method names
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
// Determine whether to implement the three instance methods in sequence
if ([self ws_performSelectorWithMethodName:setKey value:value]) {
NSLog(@ "___ % @ ___." ",setKey);
return;
} else if ([self ws_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@ "___ % @ ___." ",_setKey);
return;
} else if ([self ws_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@ "___ % @ ___." ",setIsKey);
return;
}
if(! [self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"WS_UnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****".self] userInfo:nil];
}
// Get the names of all instance variables
NSMutableArray *mArray = [self getIvarListName];
// Concatenate the required instance variable name
NSString *_key = [NSString stringWithFormat:@ "_ % @",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
// Determine whether the instance variable name is in the array of instance variables. If it exists, get the instance variable and assign a value to it
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;
} 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 an exception if the relevant instance variable cannot be found
@throw [NSException exceptionWithName:@"WS_UnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****".self.NSStringFromSelector(_cmd)] userInfo:nil];
}
- (BOOL)ws_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
// Determine whether the method can respond,
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// Call if it can respond
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
// Get the name of the instance variable
- (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;
}
Copy the code
-
- To determine
key
If there is a
- To determine
-
- Then judge the relevant ones in order
set
Methods:setName:
->_setName
->setIsName
- Concatenate the names of the three methods and call them in turn
respondsToSelector
Method to determine whether it can respond, and if it can respond, useperformSelector
Method execution call
- Then judge the relevant ones in order
-
- If the class method
accessInstanceVariablesDirectly
The return value isNO
, an exception is thrown
- If the class method
-
- If the return
YES
Gets an array of instance variable names for the class, and then based on the names of the associated instance methodsAccording to the order
Determine if it is in the array and get the instance variable if it isivar
And then assign a value to it
- If the return
-
- An exception is thrown when the instance method cannot find a value either
The valuesws_valueForKey:
The code is as follows:
- (nullable id)ws_valueForKey:(NSString *)key {
if (key == nil || key.length == 0) {
return nil;
}
// Capitalize the first letter
NSString *Key = key.capitalizedString;
// Concatenate the method name, which is partially the same as the instance variable name
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
NSString *_key = [NSString stringWithFormat:@ "_ % @",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(isKey)]) {
return [self performSelector:NSSelectorFromString(isKey)];
} else if ([self respondsToSelector:NSSelectorFromString(_key)]) {
return [self performSelector:NSSelectorFromString(_key)];
}
// Set type processing
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
// Determine whether an instance variable can be assigned directly
if(! [self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"WS_UnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****".self] userInfo:nil];
}
// Get the instance variable name
NSMutableArray *mArray = [self getIvarListName];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
// Check whether the concatenated instance variable name is in the array of instance variables. If it exists, get the instance variable and take a value
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);;
}
@throw [NSException exceptionWithName:@"WS_UnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****".self.NSStringFromSelector(_cmd)] userInfo:nil];
return @ "";
}
Copy the code
-
- To determine
key
Returns if it exists, does not exist, or is nullnil
- To determine
-
- Then judge the relevant ones in order
get
Example method:getName:
->name
->isName
->_name
- Concatenate the names of the methods and call them in turn
respondsToSelector
Method to determine whether it can respond, and if it can respond, useperformSelector
Method execution call
- Then judge the relevant ones in order
-
- If the class method
accessInstanceVariablesDirectly
The return value isNO
, an exception is thrown
- If the class method
-
- If the return
YES
Gets an array of instance variable names for the class, and then based on the names of the associated instance methodsAccording to the order
Determine if it is in the array and get the instance variable if it isivar
And then the value
- If the return
-
- When the instance method cannot find the value either, it throws an exception and returns null