KVC belongs to the Foundation framework and is not open source, so we can only know about it through official documentation

A. KVC

1.1 Definition of KVC

Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.

KVC (key-value encoding) a mechanism enabled by the NSKeyValueCoding informal protocol that allows indirect access to the properties of an object. When an object is compatible with key-value encoding, its properties can be addressed by string arguments through a compact, uniform messaging interface. This indirect access mechanism complements the direct access provided by instance variables and their associated accessor methods.

Objects typically adopt key-value coding when they inherit from NSObject (directly or indirectly),

All types that directly or indirectly inherit from NSObject, that is, almost all Objective-C objects can use KVC (some pure Swift classes and constructs do not support KVC).

1.2 the KVC API

There are four common methods of KVC

// Set key by key - (void)setValue:(nullable id)value forKey:(NSString *)key; // value by key - (nullable id)valueForKey:(NSString *)key; // set keyPath - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // keyPath - (nullable id)valueForKeyPath:(NSString *)keyPath;Copy the code

There are other methods in the NSKeyValueCoding category

/ / 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 checksetIs the correct value, makes a replacement value for the incorrect value, or rejects the new value and returns the cause of the error. - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValueforKey:(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 no KVC can find any fields or attributes related to the Key, this method is called. By default, an exception is thrown. - (nullable id)valueForUndefinedKey:(NSString *)key; // Take the value - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; // If you pass nil to Value during the SetValue method, this method will be 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

Two. The use of KVC

This is basic, but note that only data that inherits from NSObject can use KVC. Non-nsobject types need to be cast.

2.1 Accessing Object Properties

2.1.1 valueForKey & setValue: Forkey

ValueForKey: and setValue: Forkey: are used to obtain and set the property value indirectly

valueForKey: – Returns the value of a property named by the key parameter. If the property named by the key cannot be found according to the rules described in Accessor Search Patterns, then the object sends itself a valueForUndefinedKey: message. The default implementation of valueForUndefinedKey: raises an NSUndefinedKeyException, but subclasses may override this behavior and handle the situation more gracefully.

  • valueForKey: Returns the value of the property named by the key parameter. If an attribute named by key is not found according to the rules in the visitor search pattern, the object is sent to itselfvalueForUndefinedKey:The message.valueForUndefinedKey:The default implementation of theNSUndefinedKeyExceptionException, but subclasses can override this behavior and handle the situation more elegantly.

setValue:forKey:: Sets the value of the specified key relative to the object receiving the message to the given value. The default implementation of setValue:forKey: automatically unwraps NSNumber and NSValue objects that represent scalars and structs and assigns them to the property. See Representing Non-Object Values for details on the wrapping and unwrapping semantics. If the specified key corresponds to a property that the object receiving the setter call does not have, the object sends itself a setValue:forUndefinedKey: message. The default implementation of setValue:forUndefinedKey: raises an NSUndefinedKeyException. However, subclasses may override this method to handle the request in a custom manner.

  • setValue:forKey:: sets the value of the specified key for the message receiver to the given value. The default implementation will automatically representscalarandThe structure of the bodyThe NSNumber and NSValue objects are unpacked and assigned to properties. If the attribute corresponding to the specified key has no correspondingsetterImplementation, the object is sent to itselfsetValue:forUndefinedKey:The message,valueForUndefinedKey:The default implementation of theNSUndefinedKeyExceptionThe exception. However, subclasses can override this method to handle requests in a custom way.

Example:

AKPerson *person = [[AKPerson alloc] init];
   
[person setValue:@"akironer" forKey:@"name"];
NSLog(@"% @", [person valueForKey:@"name"]); Printout: akironerCopy the code

2.1.2 valueForKeyPath & setValue: forKeyPath:

valueForKeyPath: – Returns the value for the specified key path relative to the receiver. Any object in the key path sequence that is not Key-value coding Compliant for a particular key — that is, for which the default implementation of valueForKey: Cannot find an accessor method – Receives a valueForUndefinedKey: message.

  • valueForKeyPath:: returns the receiver’s specificationkey pathOn the value.key pathAny object in the path sequence that does not match the key-value encoding for a particular key is receivedvalueForUndefinedKey:The message.

setValue:forKeyPath: – Sets the given value at the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key receives a setValue:forUndefinedKey: message.

  • setValue:forKeyPath:: Specifies the receiver of the messagekey pathSets the value of thekey pathAny object in the path sequence that does not match the key-value encoding for a particular key is receivedsetValue:forUndefinedKey:The message

Example:

AKTeacher *teacher = [[AKTeacher alloc] init];
teacher.subject    = @"iOS";
person.teacher     = teacher;
[person setValue:@"The Way to iOS" forKeyPath:@"teacher.subject"];
NSLog(@"% @",[person valueForKeyPath:@"teacher.subject"]); Printout: iOS advanced pathCopy the code

2.1.3 dictionaryWithValuesForKeys: & setValuesForKeysWithDictionary:

-> dictionaryWithValuesForKeys: – Returns the values for an array of keys relative to the receiver. The method calls valueForKey: for each key in the array. The returned NSDictionary contains values for all the keys in the array.

  • Return receiver’skeyThe value of array. This method will be used for each of thekeycallvalueForKey:. The returnedNSDictionaryContains the values of all the keys in the array.

setValuesForKeysWithDictionary: – Sets the properties of the receiver with the values in the specified dictionary, using the dictionary keys to identify the properties. The default implementation invokes setValue:forKey: for each key-value pair, substituting nil for NSNull objects as required.

  • setValuesForKeysWithDictionary:: Identifies attributes with dictionary keys, setting the corresponding value in the specified dictionary to the attribute value of the message receiver. The default implementation calls each key/value pairsetValue:forKey:. You need to set thenilreplaceNSNull

Collection objects, such as NSArray, NSSet, and NSDictionary, can’t contain nil as a value. Instead, you represent nil values using the NSNull object.

  • NSArray NSSetNSDictionarySuch a collection object cannot containnilAs a value, it can be usedNSNull objectInstead ofnilValue.
[person setValuesForKeysWithDictionary:@{@"name": @"akironer"The @"age": @ @ (18)}"hobby": [NSNULL null]]; NSLog(@"% @", [person dictionaryWithValuesForKeys:@[@"name"The @"age"]]); Printout: {age = 18; name = akironer; hobby = null; }Copy the code

2.2 Accessing collection properties

// Method 1: person.array = @[@"1"The @"2"The @"3"];
NSArray *array = [person valueForKey:@"array"]; // Create a new array with the value of array = @[@"100"The @"2"The @"3"];
[person setValue:array forKey:@"array"];
NSLog(@"Method 1: %@",[person valueForKey:@"array"]); NSMutableArray *ma = [Person mutableArrayValueForKey:@"array"];
ma[0] = @"100";
NSLog(@"Method 2: %@",[person valueForKey:@"array"]); Print output: Method 1: (100, 2, 3) Method 2: (100, 2,)Copy the code

A more efficient way to manipulate elements inside a collection object is to use the variable proxy method provided by KVC. KVC provides us with three different variable proxy methods:

  1. mutableArrayValueForKey: & mutableArrayValueForKeyPath:The returned proxy object is represented as an NSMutableArray object
  2. mutableSetValueForKey: & mutableSetValueForKeyPath:: The returned proxy object behaves as an NSMutableSet object
  3. mutableOrderedSetValueForKey: & mutableOrderedSetValueForKeyPath:The returned proxy object is represented as an NSMutableOrderedSet object

2.3 Set operators

When using valueForKeyPath:, you can use the set operators to achieve some efficient operations.

  1. Aggregate operator
  • @avg: Returns the average value of the specified properties of the operation object
  • @count: Returns the number of specified properties of the operation object
  • @max: Returns the maximum value of the specified property of the operation object
  • @min: Returns the minimum value of the specified property of the operation object
  • @sum: Returns the sum of the specified attribute values of the operation object
  1. Array operator
  • @distinctUnionOfObjects: returns a collection of properties specified by the operation object — de-duplication
  • @unionOfObjects: Returns the collection of properties specified by the operation object
  1. Nested operators
  • @distinctUnionOfArrays: Returns the set of properties specified by the operation object (nested set) — de-duplicated, returns NSArray
  • @unionOfArrays: Returns the collection of specified properties of the operation object (collection)
  • @distinctUnionOfSets: returns the set of specified attributes of the operation object (nested set) — de-duplicated, returns NSSet

2.4 Accessing non-object properties

Non-object properties fall into two categories:

  • The basic data type, namely, scalar
  • Struct.

2.4.1 Accessing Basic Data Types (Scalars)

Common basic data types need to be wrapped as NSNumber objects when setting properties

2.4.2 Accessing structures

In addition to NSPoint, NSRange, NSRect and NSSize, you also need to convert NSValue to a customized structure.

typedef struct {
    floatx, y, z; } ThreeFloats; Floats = {1., 2., 3.}; NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)]; [personsetValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"% @",reslut); // The value ThreeFloats result; [reslut getValue:&result] ; NSLog(@"%f - %f - %f",result.x, result.y, result.z); Print: {length = 12, bytes = 0 x0000803f0000004000004040} 1.000000-2.000000-3.000000Copy the code

Three. KVC principle — search rules

Before learning the search rules of KVC, it is important to understand the role of an attribute, which plays a very important role in the search process. This property indicates whether the value of the instance variable is allowed to be read, and if YES, the value of the property instance variable is read from memory during a KVC lookup.

@property (class, readonly) BOOL accessInstanceVariablesDirectly;
Copy the code

3.1 basic getter

ValueForKey: The method searches the object for a pattern after the caller passes in the key by following these steps:

  1. In order toget<Key> <key> is<Key>As well as_<key>To find if there are corresponding methods in the object.
  • If found, attach the method return value and jump to step 5
  • If not, go to step 2
  1. If you don’t find itSimple getter methodsMethod to find if there areCountOf < Key > method ObjectIn < Key > AtIndex: method(corresponding to theNSArray classThe original method defined) andThe < key > AtIndexes: methodObjectsAtIndexes: (corresponding to NSArray method objectsAtIndexes:
  • If you find the first of these (countOf<Key>), and find at least one of the other two, create a response allNSArrayMethod, and returns the object. (The translation is eithercountOf<Key> + objectIn<Key>AtIndex:, eithercountOf<Key> + <key>AtIndexes:, eithercountOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:)
  • If not, go to step 3
  1. If you don’t find itSimple NSArray method, find the namecountOf<Key> enumeratorOf<Key> memberOf<Key>The three methods (corresponding toNSSet classThe original method defined)
  • If all three methods are found, a response all is createdNSSetMethod, and returns the object
  • If not, go to Step 4
  1. Judgment class methodaccessInstanceVariablesDirectlyThe results of
  • If YES is returned, the_<key> _is<Key> <key> is<Key>Find member variables in order of. If found, attach the member variable to step 5; if not, go to step 6
  • Return NO, skip to step 6
  1. Determine the value of the fetched attribute
  • If the property value is an object, return it directly
  • If the attribute value is not an object but can be converted to an NSNumber type, an NSNumber type is returned
  • If the attribute value is not an object and cannot be converted to an NSNumber type, an NSValue is returned
  1. callvalueForUndefinedKey:, thrown by defaultNSUndefinedKeyExceptionException, but subclasses that inherit from NSObject can override that method to avoid crashing and do something about it

3.2 basic setter

  1. According to theset<Key>: _set<Key>Find the corresponding methods in the object in order
  • The direct call set was found
  • Jump step 2 was not found
  1. judgeaccessInstanceVariablesDirectlyThe results of
  • Is YES, according to_<key> _is<Key> <key> is<Key>Search for member variables in the order of, and assign values if found; If not, go to step 3
  • If the value is NO, go to step 3
  1. callSetValue: forUndefinedKey:. Thrown by defaultNSUndefinedKeyExceptionException, but subclasses that inherit from NSObject can override the method to avoid crashing and do something about it

3.3 The compiler automatically implements the getter setter

Here are the differences between instance variables, member variables, and attributes:

  • Member variables: variables declared inside the @interface parentheses
  • A member variable is actually made up of two parts: an instance variable + a primitive datatype variable
  • Property = member variable + getter method + setter method

Instead of overwriting the getter and setter methods of the property and declaring the corresponding instance variables, the compiler will do it for us. So how many getters and setters are generated for as many properties?

Obviously, compilers aren’t that stupid. The compiler uses general principles in objc-accessors.mm to provide the same entry for all properties. Setter methods call different methods depending on the modifier, and finally call the reallySetProperty method.

Iv. The use of KVC

KVC is an indispensable tool in iOS development and is the foundation of much of the dark magic of iOS development. List the usage scenarios of KVC.

4.1 Dynamic Values and set values

The most basic usage, I believe we are very attribute

4.2 Accessing and modifying private variables

Private attributes in a class are not directly accessible in Objective-C, but KVC is.

4.3 Model and dictionary conversion

KVC and Objc runtime combination techniques are used to complete the model and dictionary conversion

4.4 Modifying internal Control properties

Prior to iOS 13, we could use KVC to get and set system private properties, but since iOS 13, this method has been disabled. I believe that many students have encountered the problem of ACCESS restriction of KVC when adapting iOS 13.

The placeHolderText UITextField that already cannot be modified, for example, here are two short answer change train of thought, want to understand you can refer to about iOS 13 KVC access restrictions some processing

  1. throughattributedPlaceholderAttribute changesPlaceholdercolor
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"Please enter placeholder text." attributes: @{NSForegroundColorAttributeName:[UIColor redColor], NSFontAttributeName:textField.font }];

textField.attributedPlaceholder = attrString;
Copy the code
  1. forUITextFieldLet me rewrite this method
- (void)resetTextField: (UITextField *)textField
{
 	Ivar ivar =  class_getInstanceVariable([textField class], "_placeholderLabel");
 	
    UILabel *placeholderLabel = object_getIvar(textField, ivar);
    placeholderLabel.text = title;
    placeholderLabel.textColor = color;
    placeholderLabel.font = [UIFont systemFontOfSize:fontSize];
    placeholderLabel.textAlignment = alignment;
}
Copy the code

5. Exception handling and correctness verification

5.1 Setting a Null Value: setNilValueForKey

Setting a null value while setting a value can be listened for by overriding setNilValueForKey

In the default implementation, when you attempt to set a non-object property to a nil value, the key-value coding compliant object sends itself a setNilValueForKey: message. The default implementation of setNilValueForKey: raises an NSInvalidArgumentException, but an object may override this behavior to substitute a default value or a marker value instead, as described in Handling Non-Object Values.

In the default implementation, when you try to set a non-object property to nil, the KVC object sends itself a setNilValueForKey: message. SetNilValueForKey default implementation will trigger a NSInvalidArgumentException, but objects can override this behavior to replace the default value or tag.

Given that an invocation of -setValue:forKey: would be unable to set the keyed value because the type of the parameter of the corresponding accessor method is an NSNumber scalar type or NSValue structure type but the value is nil, set the keyed value using some other mechanism.

SetNilValueForKey is triggered only when null values are assigned to NSNumber or NSValue data. In the following example, subject does not trigger

@implementation LGPerson
- (void)setNilValueForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        NSLog(@"Are you stupid: set %@ to null?",key);
        return 0;
    }
    [super setNilValueForKey:key];
}
@ end

[person setValue:nil forKey:@"age"]; 
[person setValue:nil forKey:@"subject"]; // subject does not trigger - only NSNumber - NSValueCopy the code

5.2 UndefinedKey: setValue:forUndefinedKey

For undefined key, can be rewritten setValue: forUndefinedKey:, valueForUndefinedKey: to listen.

Such as:

When we turn the dictionary model, for example, the server returns an id field, but for the client id system fields, keeping can override setValue: forUndefinedKey: methods and id parameter in internal treatment assignment.

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"id"]) {
        self.userId = [value integerValue]; }}Copy the code

5.3 Attribute Verification

The KVC can be authenticated by the following two methods, including key and keyPath.

The validation method needs to be called manually and will not be called automatically during KVC

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
Copy the code

The method works as follows:

  1. -(BOOL)validate:error;
  2. If it does, it will return NO or YES according to the custom logic in the implementation method; If this method is not implemented, YES is returned by default

The following is an example of using validation methods. In the internal implementation of the validateValue method, if there is a problem with the value or key passed in, the error can be indicated by returning NO and setting the NSError object.

AKPerson* person = [[AKPerson alloc] init];
NSError* error;
NSString* name = @"John";
if(! [person validateValue:&nameforKey:@"name" error:&error]) {
    NSLog(@"% @",error);
}
Copy the code

6. Summary

  1. KVC is aNSKeyValueCodingThe mechanism provided by implicit protocols.
  2. KVC byvalueForKey:valueForKeyPath:Regardless of the set type, the specific value process is as follows:
  • In order toget<Key> <key> is<Key> _<key>The sequential lookup method of
  • If you can’t find a method, use a class methodaccessInstanceVariablesDirectlyDetermines whether a member variable can be read to return an attribute value
  • In order to_<key> _is<Key> <key> is<Key>Find member variables in order of
  1. KVC bysetValueForKey:setValueForKeyPath:Regardless of the set type, the specific setting process is as follows:
  • In order toset<Key> _set<Key>The sequential lookup method of
  • If can’t find the method, through the class methods accessInstanceVariablesDirectly can determine whether to return to the set values through member variables In order to_<key> _is<Key> <key> is<Key>Find member variables in order of

This time, we completed the exploration of KVC according to the official documents of Apple. In fact, Apple’s English notes and official documents are written very carefully. When we explore the bottom layer of iOS, the document thinking is very important.