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 objectset<Key>:or_set<Key>Accessors (that is, methods) to.
  • 2. If no accessor is found and the class methodaccessInstanceVariablesDirectlyreturnYES, 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 calledsetValue: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 orderget<Key>.<key>.is<Key>_<key>Accessors (that is, methods) to.
  • 2. If no accessor is found and the class methodaccessInstanceVariablesDirectlyreturnYES, 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, callvalueForUndefinedKey: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. UsesetValue: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 methodaccessInstanceVariablesDirectlyreturnYES, 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 callsetValue: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 usevalueForKey: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 methodaccessInstanceVariablesDirectlyreturnYES, 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, invokevalueForUndefinedKey: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.