1, the preface
When you think about KVC, the first thing most people think about is setValue: forKey: and setValue: ForKeyPath:, which is what we call key-value coding. Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects use to provide indirect access to their properties. When an object conforms to a key-value encoding, its properties can be addressed with string parameters through a concise, uniform messaging interface. Detailed explanation can be found in the official documentation. Now let’s explore the underlying principles of KVC with me.
2. Preliminary study on KVC
1. Several ways of using KVC
Create a Person class and add some properties to the class.
Person.h
typedef struct {
float x, y, z;
} ThreeFloats;
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, strong) NSMutableArray *mArray;
@property (nonatomic, assign) int age;
@property (nonatomic) ThreeFloats threeFloats;
@property (nonatomic, strong) Student *student;
@end
Copy the code
1️ basic type of use
- (void)setValue:(nullable id)value forKey:(NSString *)key; - (nullable id)valueForKey:(NSString *)key; // Assign the person object name attribute and the value [personsetValue:@"Time flies." forKey:@"name"];
[person valueForKey:@"name"];
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; Student * Student = [[Student alloc] init]; student.name = @"xx";
person.student = student;
[person setValue:@"Students" forKeyPath:@"student.name"];
NSLog(@"% @",[person valueForKeyPath:@"student.name"]);
Copy the code
2️ collection type use
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; // Change the first value of the immutable array, person.array = @[@"1"The @"2"The @"3"]; NSArray *array = @[@"1"The @"2"The @"3"];
[person setValue:array forKey:@"array"]; ArrayM = [Person mutableArrayValueForKey:@"array"];
arrayM[0] = @"10";
Copy the code
3️ dictionary use
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues; NSDictionary* dict = @{@"name": @"Time flies."The @"age": @}; [personsetValuesForKeysWithDictionary:dict];
Copy the code
4️ access to non-object properties
ThreeFloats floats = {1., 2., 3.}; Floats objCType:@encode(ThreeFloats)] NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)]; [personsetValue:value forKey:@"threeFloats"];
NSValue *reslutValue = [person valueForKey:@"threeFloats"];
NSLog(@"value = %@",reslutValue); // Create a structure of the same type to receive reslutValue ThreeFloats th; [reslutValue getValue:&th] ; NSLog(@"%f - %f - %f",th.x,th.y,th.z);
Copy the code
2. SetValue :forKey: Exploration of underlying principles
How do we assign a value to our object when we call setValue:forKey:?
- 1. The first step is to check if there is any in the object
set<Key>:
or_set<Key>
Accessors (that is, methods) to. - 2. If no accessor is found and the class method
accessInstanceVariablesDirectly
returnYES
, will search for the name in order_<key>
,_is< key>
,<key>
或<key>
If found, directly set the variable and complete. - 3. If neither method nor instance variable is found, it is called
setValue:forUndefinedKey:
Methods.
Note: “key” here refers to the name of the member variable. The writing format must comply with KVC naming rules.
SetKey: method validation
Create a Person class, and add four instance variables, as well as add elegantly-named setName:, _setName:, accessInstanceVariablesDirectly method. (The following verification is based on this object, instance variables remain unchanged, method changes)
@interface Person : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@implementation Person
//MARK: - setKey flow analysis - (void)setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
@end
Person *person = [[Person alloc] init];
[person setValue:@"Time flies." forKey:@"name"];
Copy the code
- Result: Sequential access order
-[Person setName: [Person _setName:] -[Person _setName:] -Copy the code
If you will be all set method annotations, accessInstanceVariablesDirectly return NO, will be submitted to the ‘[0 < Person x6000033a5860 > setValue: forUndefinedKey:] : This class is not key value coding-compliant for the key XXX.’ crashes.
2. After accessInstanceVariablesDirectly returns YES instance variables
@implementation Person
#pragma mark - Turns instance variable assignment off or on
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
@end
Person *person = [[Person alloc] init];
[person setValue:@"Time flies." forKey:@"name"];
NSLog(@"_name:%@-_isName:%@-name:%@-isName%@",person->_name,person->_isName,person->name,person->isName);
NSLog(@"_isName:%@-name:%@-isName:%@",person->_isName,person->name,person->isName);
NSLog(@"name:%@-isName%@",person->name,person->isName);
NSLog(@"isName:%@",person->isName);
Copy the code
Will return to YES accessInstanceVariablesDirectly, then the instance variable _name, _isName, name, isName annotation run in sequence (NSLog will, in turn, annotated oh), the result will be the following output.
1.KVC explore [4370:1716833] _name: KVC -_isName:(null)-name:(null) -isname :(null) 2.KVC explore [4417:1720210] _isName: KVC search [4445:1722057] name: KVC search [4468:1723450] isName: KVC search [4468:1723450] isName: KVC searchCopy the code
ValueForKey: Exploring underlying principles
1.Search the instance for the first accessor method found with a name like get<Key>, <key>, is<Key>, or _<key>, inThat order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step. 4.If no simple accessor method or group of collection access methods is found, andifThe receiver 's class method accessInstanceVariablesDirectly returns YES, searchfor an instance variable named _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6.
5.If the retrieved property value is an object pointer, simply return the result.
If the value is a scalar type supported by NSNumber, store it in an NSNumber instance and return that.
If the result is a scalar type not supported by NSNumber, convert to an NSValue object and return that.
6.If all else fails, invoke valueForUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.
Copy the code
In general, according to official documents:
- 1. The first step is to search in order
get<Key>
.<key>
.is<Key>
或_<key>
Accessors (that is, methods) to. - 2. If no accessor is found and the class method
accessInstanceVariablesDirectly
returnYES
, search for names in order_<key>
._is<key>
.<key>
Or,is<Key>
If found, the value of the instance variable is directly obtained and converted to the corresponding type to return. - 3. If neither method nor instance variable is found, call
valueForUndefinedKey:
Methods.
1. GetKey: method verification
Run the following methods commented from top to bottom.
@implementation Person //MARK: -valueForKey - get<Key>, <Key>, is<Key>, or _< Key>, - (NSString *)getName{return NSStringFromSelector(_cmd);
}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
Person *person = [[Person alloc] init];
NSLog(@Values: "% @",[person valueForKey:@"name"]);
@end
Copy the code
- Result: Sequential access order
KVC Exploration [4725:1958586] Value :getName 2.KVC Exploration [4749-1978501] Value: Name 3.KVC Exploration [4749-1978501] Value :isName 4. Values: _nameCopy the code
If all the get method, accessInstanceVariablesDirectly return NO, will be submitted to the ‘[0 < Person x600001ce28b0 > valueForUndefinedKey:] : This class is not key value coding-compliant for the key XXX.’ crashes.
2. After accessInstanceVariablesDirectly returns YES instance variables
#pragma mark - Turns instance variable assignment off or on
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
Person *person = [[Person alloc] init];
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@Values: "% @",[person valueForKey:@"name"]);
Copy the code
Will return to YES accessInstanceVariablesDirectly, then the instance variable _name, _isName, name, isName annotation run in sequence (assignment code will, in turn, annotated oh), the result will be the following output.
KVC Exploration [4792:2019099] Value :_isName 3.KVC Exploration [4792:2019099] Value :name 4.KVC Exploration [4792:2019099] Values: isNameCopy the code
4. KVC anti-crash processing
When we use setValue: forKey: or valueForKey:, because the key to his hand and no hint, so will probably not be careful to write wrong, then will be submitted to the setValue: forUndefinedKey: Or valueForUndefinedKey:. How do you prevent that? – (void)setValue:(id)value forUndefinedKey:(NSString *)key and – (id)valueForUndefinedKey:(NSString *)key
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"Come");
}
- (id)valueForUndefinedKey:(NSString *)key {
returnnil; } //MARK: void anti-crash - (void)setNilValueForKey:(NSString *)key{
NSLog(@"Set %@ to null",key); } //MARK: - (BOOL)validateValue:(inout ID _Nullable __autoreleasing *)ioValueforKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError{
if([inKey isEqualToString:@"name"]){
[self setValue:[NSString stringWithFormat:@"Inside change: %@",*ioValue] forKey:inKey];
return YES;
}
*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ is not a property of %@".inKey,self] code:10088 userInfo:nil];
return NO;
}
Copy the code
5. Develop
In addition to KVC can assign values to object attributes, in fact, we often use point syntax, example: person.name = @” time flies “; , this method will eventually call the reallySetProperty function to assign a value to the property, and depending on the property modifier, the parameters are different, see the source code at a time.
Void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset,true.true.false); Void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) { reallySetProperty(self, _cmd, newValue, offset,false.true.false); } // omits some attributes of other attribute modifiers, Static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {if(offset == 0) {// Call changeIsa(Class newCls) to change the newValue object_setClass(self, newValue) to which isa points;return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if(copy) {// Shallow copy newValue = [newValue copyWithZone:nil]; }else if(mutableCopy) {// Deep copy newValue = [newValue mutableCopyWithZone:nil]; }else {
if (*slot == newValue) return; // Reference count + 1 newValue = objc_retain(newValue); }if(! atomic) { oldValue = *slot; *slot = newValue; }else{ spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } // release objc_release(oldValue); }Copy the code
6. Summary
- 1. Use
setValue:forKey:
“, will first go to the order to find whether the object hasset<Key>:
,_set<Key>
,setIs<Key>:
(thoughsetIs<Key>:
This method is not written on the official documentation, but it is called) method, if there is a call to assign. - 2. If not found, and class method
accessInstanceVariablesDirectly
returnYES
, will search for the name in order_<key>
,_is< key>
,<key>
或<key>
If found, directly set the variable and complete. - 3. If the method and instance variables are not found again, the system will call
setValue:forUndefinedKey:
Method if the object is not implementedsetValue:forUndefinedKey:
Will be submitted to the'[<Person 0x60000346a760> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key xxx.'
The collapse. - 4. In use
valueForKey:
First look for objects in order to see if they existget<Key>
.<key>
.is<Key>
或_<key>
Method, if any, return. - 5. If not found, and class method
accessInstanceVariablesDirectly
returnYES
, the names are searched in order_<key>
._is<key>
.<key>
Or,is<Key>
If found, the value of the instance variable is directly obtained and converted to the corresponding type to return. - 6. If no method or instance variable is found, invoke
valueForUndefinedKey:
Methods. If the object is not implementedvalueForUndefinedKey:
Will be submitted to the'[<Person 0x600001ce28b0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key xxx.'
The collapse.