Blog link KVC implementation principle
KVC is KeyValueCoding, which is defined in nsKeyValuecoding. h file. KVC provides a mechanism for indirectly accessing its attribute methods or member variables through strings. The implementation of KVC mainly relies on its search rules.
Search rules
In assignment, we use – (void)setValue:(id)value forKey:(NSString *)key or (void)setValue:(id)value forKeyPath:(NSString *)keyPath; Perform KVC assignment. So in this case, we’re going to use – (id)valueForKey:(NSString *)key; Or – (id)valueForKeyPath:(NSString *)keyPath; .
When KVC operates through key or keyPath, it can look up attribute methods, member variables, etc. Various names can be compatible during the search. Specific search rules can be found in the official KVC documentation.
The IMPLEMENTATION of KVC relies heavily on setter and getter methods, so you need to comply with Apple’s specifications for naming. In addition in the process of search accessInstanceVariablesDirectly the read-only property also plays an important role. This property indicates whether the value of the instance variable is allowed to be read. If YES, the value of the property instance variable is read from memory during the KVC lookup. The default value is YES.
Assignment principle
Take setValue:forKey: as an example, its internal implementation mainly includes the following steps:
-
Find the corresponding named setter method in the order set
:, _set
, if found, call the method and pass the value in (convert objects as needed);
-
If have not found a setter method, but accessInstanceVariablesDirectly class property returns YES, then the _ < key >, _is < key >, < the key >, is < key > order to find a corresponding instance variable. If found, value is assigned to the instance variable;
-
If have not found a setter method or instance variables, call the setValue: forUndefinedKey: method, the default throw an exception, but an NSObject subclass can recommend appropriate actions.
Next, we use the code for the relevant tests:
Experiment 1: Validate setter methods
// model1
@interface KVCTestModel1 : NSObject
@end
@implementation KVCTestModel1
- (void)setName:(NSString *)name {
NSLog(@"%s", __func__);
}
- (void)_setName:(NSString *)name {
NSLog(@"%s", __func__);
}
@end
// model2
@interface KVCTestModel2 : NSObject
@end
@implementation KVCTestModel2
- (void)_setName:(NSString *)name {
NSLog(@"%s", __func__); } @end // model3 @interface KVCTestModel3 : nsobject@end@implementation KVCTestModel3 @end - (void)_testKVC {KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init]; [model1setValue:@"Nero" forKey:@"name"];
KVCTestModel2 *model2 = [[KVCTestModel2 alloc] init];
[model2 setValue:@"Nero" forKey:@"name"];
KVCTestModel3 *model3 = [[KVCTestModel3 alloc] init];
[model3 setValue:@"Nero" forKey:@"name"];
}
Copy the code
The result is as follows:
Experiment 2: Verify the instance variables
// model1 @interface KVCTestModel1 : NSObject { NSString *_name; NSString *_isName; NSString *name; NSString *isName; } @end // model2 @interface KVCTestModel2 : NSObject { NSString *_isName; NSString *name; NSString *isName; } @end // model3 @interface KVCTestModel3 : NSObject { NSString *name; NSString *isName; } @end // model4 @interface KVCTestModel4 : NSObject { NSString *isName; } @end // call - (void)_testKVC {self.kvcTestModel1 = [[kvcTestModel1 alloc] init]; [self.kvcTestModel1setValue:@"Nero" forKey:@"name"];
self.kvcTestModel2 = [[KVCTestModel2 alloc] init];
[self.kvcTestModel2 setValue:@"Nero" forKey:@"name"];
self.kvcTestModel3 = [[KVCTestModel3 alloc] init];
[self.kvcTestModel3 setValue:@"Nero" forKey:@"name"];
self.kvcTestModel4 = [[KVCTestModel4 alloc] init];
[self.kvcTestModel4 setValue:@"Nero" forKey:@"name"];
}
Copy the code
The result is as follows:
In addition if set accessInstanceVariablesDirectly returned to NO, even if there is the instance variable name of conform to the naming conventions, KVC cannot assignment success; SetValue: forUndefinedKey: default will throw an exception, you can use the rewrite this method is used to intercept.
The assignment principle flow chart is as follows:
The principle of value
Taking valueForKey: as an example, its internal implementation mainly includes the following steps:
-
The instance is searched by getter method, and the methods that meet the rules are searched in the order of GET
,
, is
, and _< Key>. If there are any, the corresponding methods are called.
-
If not found simple getters, and return to YES on accessInstanceVariablesDirectly class method is the search for a named _ < key >, _is < key >, < the key >, is < key > instance;
-
If the return value is an object pointer, the result is returned directly. If the return value is an underlying data type, but the underlying data type is supported by NSNumber, store it as NSNumber and return it. If the return value is an underlying data type that does not support NSNumber, it is stored by NSValue and returned.
-
If all of the above fails, the valueForUndefinedKey: method is invoked, which throws an exception by default, but subclasses can override this method.
Similar to the previous assignment principle experiment, no validation code will be added here. In addition, valueForKey: may return an array or other collection type, so there are some other rules between steps 1 and 2 above that are used to determine whether it is an array or other collection type, but I don’t think that ignoring these rules conflicts with the overall understanding of the process. So it is ignored (the details can be found in the official KVC document). .
Supplement:
Interview question analysis: can KVC trigger KVO
The answer is yes.
The test code is as follows:
@interface KVCTestModel1 : NSObject { @public NSString *_name; } @end @implementation KVCTestModel1 @end KVCTestModel1 *model1 = [[KVCTestModel1 alloc] init]; [model1 addObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNew context:nil]; // Modify the member variable model1 -> _name = @"Nero1";
NSLog(@"% @", [model1 valueForKey:@"name"]); // Manually trigger KVO [model1 willChangeValueForKey:@"name"];
model1 -> _name = @"Nero2";
[model1 didChangeValueForKey:@"name"];
NSLog(@"% @", [model1 valueForKey:@"name"]); // assign KVC [model1]setValue:@"Nero3" forKeyPath:@"name"];
NSLog(@"% @", [model1 valueForKey:@"name"]);
[model1 removeObserver:self forKeyPath:@"name"];
Copy the code
Print result:
From the above code, we can assume that when assigning a value to a variable in the manner of KVC, it will determine whether the object uses KVO, and if so, it will trigger KVO.