KVC: Full name Key Value Coding, commonly known as “Key Value Coding”, is explained in detail on the official website. It allows developers to access properties of an object directly through the Key name, or to assign values to properties of an object, without calling explicit access methods. This allows you to access and modify the properties of an object dynamically at run time. Not at compile time, which is one of the dark arts of iOS development. So how does it work?

One: Basic use

Common API

- (nullable id)valueForKey:(NSString *)key; - (void)setValue:(nullable id)value forKey:(NSString *) Key; // set the value by Key - (nullable id)valueForKeyPath:(NSString *)keyPath; // set the value to KeyPath. - (void)setValue:(nullable id)value forKeyPath:(NSString *) KeyPath; // set the value to KeyPathCopy the code

Other apis

// The default is YES. If it returns to YES, if not found the set < Key > method, according to _key, _isKey, Key, isKey sequential search member variable, returns NO will not search + (BOOL) accessInstanceVariablesDirectly; // Check whether the key value is correct. - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError; // If the key does not exist and no fields related to the key are found, this method is called and an exception is thrown by default. - (nullable id)valueForUndefinedKey:(NSString *)key; - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; - (void)setNilValueForKey:(NSString *)key; (void)setNilValueForKey:(NSString *)key; // a set of values for keys, which can be used to convert models into dictionaries - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;Copy the code

Sample code:

#import <Foundation/Foundation.h> #import "WYStudent.h" typedef struct { float x, y, z; } ThreeFloats; 🌹 @interface WYPerson: NSObject{@public NSString *myName; } @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) WYStudent *student; @end 🌹 @interface WYStudent: NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *subject; @property (nonatomic, copy) NSString *nick; @property (nonatomic, assign) int age; @property (nonatomic, assign) int length; @property (nonatomic, strong) NSMutableArray *penArr; @endCopy the code

1 Access object properties

SetValue and valueForKey

WYPerson *person = [[WYPerson alloc] init]; [person setValue:@" @" forKey:@"name"]; NSLog(@"person name: %@", [person valueForKey:@"name"]); The result: the name of person is: AcloudCopy the code

ValueForKeyPath: and setValue: ForKeyPath

WYStudent *student = [WYStudent alloc]; [person setValue:@"Swift" forKeyPath:@"student.subject"]; NSLog(@"%@",[person valueForKeyPath:@"student.subject"]); Print result: The name of person is: SwiftCopy the code

2 Access the collection properties

The first method: assign directly from a new array

NSArray *array = @[@"100",@"2",@"3"]; [person setValue:array forKey:@"array"]; NSLog(@"%@",[person valueForKey:@"array"]); Print results: 100,2,3Copy the code

Second method: Take the array and save it as a mutable array, then modify it (recommended)

WYPerson *person = [[WYPerson alloc] init]; person.array = @[@"1",@"2",@"3"]; NSMutableArray *mArray = [person mutableArrayValueForKey:@"array"]; mArray[0] = @"200"; [mArray addObject:@(100)]; NSLog(@"%@",[person valueForKey:@"array"]); Print result: 200,2,3,100Copy the code

MutableArrayValueForKey: This method returns a proxy object with a mutable array of properties for the passed key. Apple provides a friendlier method for collection objects.

  • MutableArrayValueForKey: and mutableArrayValueForKeyPath:

    • These return a proxy object that behaves like an NSMutableArray object.
    • The returned proxy object appears as oneNSMutableArrayobject
  • MutableSetValueForKey: and mutableSetValueForKeyPath:

    • These return a proxy object that behaves like an NSMutableSet object.
    • The returned proxy object appears as oneNSMutableSetobject
  • mutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath:

    • These return a proxy object that behaves like an NSMutableOrderedSet object.

    • The returned proxy object is represented as an NSMutableOrderedSet object

3 Accessing the Dictionary

SetValuesForKeysWithDictionary: used to modify the Model of the corresponding key attributes (turn dictionary Model)

DictionaryWithValuesForKeys: input a set of key, returns the set of key corresponding to the attribute, then form a dictionary

An 🌰

NSDictionary* dict = @{ @"name":@"Cooci", @"nick":@"KC", @"subject":@"iOS", @"age":@18, @"length":@180 }; WYStudent *p = [[WYStudent alloc] init]; / / dictionary model [p setValuesForKeysWithDictionary: dict]; NSLog(@"%@",p); NSArray *array = @[@"name",@"age"]; NSDictionary *dic = [p dictionaryWithValuesForKeys:array] NSLog(@"%@",dic); 🌹 <WYStudent: 0x600003636eb0> age = 18; name = Cooci; * * * *}Copy the code

Set operator

Set operators can be divided into three broad categories:

  • Aggregate operator

    • @avg: returns the specified property of the operation objectThe average
    • @count: Returns the operation object specifiedNumber of attributes
    • @max: returns the specified property of the operation objectThe maximum
    • @min: returns the specified property of the operation objectThe minimum value
    • @sum: Returns the operation object specifiedSum of attribute values
  • Array operator

    • @distinctUnionOfObjects: Returns the operation objectSpecify a collection of attributes – de-duplicate
    • @unionOfObjects: Returns the operation objectSpecifies a collection of attributes
  • Nested operators

    • @distinctUnionOfArrays: Returns the action object (nested collection)Specify a collection of attributes – de-duplicateIs returnedNSArray
    • @unionOfArrays: Returns the operation object (collection)Specifies a collection of attributes
    • @distinctUnionOfSets: Returns the action object (nested collection)Specify a collection of attributes – de-duplicateIs returnedNSSet

The aggregation operator 🌰

NSMutableArray *personArray = [NSMutableArray array]; for (int i = 0; i < 6; i++) { WYStudent *p = [WYStudent new]; NSDictionary* dict = @{ @"name":@"Tom", @"age":@(18+i), @"nick":@"Cat", @"length":@(175 + 2*arc4random_uniform(6)), }; [p setValuesForKeysWithDictionary:dict]; [personArray addObject:p]; } NSLog(@"%@", [personArray valueForKey:@"length"]) /// Average height Float AVg = [[personArray valueForKeyPath:@"@avg. Length "] floatValue]; NSLog(@"%f", avg); int count = [[personArray valueForKeyPath:@"@count.length"] intValue]; NSLog(@"%d", count); int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue]; NSLog(@"%d", sum); int max = [[personArray valueForKeyPath:@"@max.length"] intValue]; NSLog(@"%d", max) int min = [[personArray valueForKeyPath:@"@min.length"] intValue] NSLog(@"%d", min);Copy the code

The array operator 🌰

NSMutableArray *personArray = [NSMutableArray array]; for (int i = 0; i < 6; i++) { WYStudent *p = [WYStudent new]; NSDictionary* dict = @{ @"name":@"Tom", @"age":@(18+i), @"nick":@"Cat", @"length":@(175 + 2*arc4random_uniform(6)), }; [p setValuesForKeysWithDictionary:dict]; [personArray addObject:p]; } NSLog(@"%@", [personArray valueForKey:@"length"]); NSArray* arr1 = [personArray valueForKeyPath:@" @unionofObjects.length "]; NSLog(@"arr1 = %@", arr1); / / return a collection of objects specified attribute to heavy NSArray * arr2 = [personArray valueForKeyPath: @ "@ distinctUnionOfObjects. Length"]. NSLog(@"arr2 = %@", arr2);Copy the code

5 Access non-object properties

Non-object properties fall into two categories: basic data types and structs.

For primitive data types, the common primitive data types need to be wrapped as NSNumber when setting properties, and then use their respective reading methods when reading values, such as doubleValue when reading a scalar of type double

For struct types, you need to convert to NSValue

Floats = {1.,2.,3.}; NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)]; [person setValue:value forKey:@"threeFloats"]; // Value NSValue *value1 = [person valueForKey:@"threeFloats"]; NSLog(@"%@",value1); ThreeFloats th; [value1 getValue:&th]; NSLog(@"%f-%f-%f",th.x,th.y,th.z);Copy the code

6 Attribute Verification

This function is through validateValue: forKey: error:

It should be noted that this method will not be called by the system, we need to manually implement

@implementation WYPerson - (BOOL)validateName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ NSString* name = *value; name = name.capitalizedString; if ([name isEqualToString:@"Not-name"]) { return NO; } return YES; } @end - (void)viewDidLoad { [super viewDidLoad]; WYPerson *person = [[WYPerson alloc]init]; NSError* error; NSString *value = @"Not-name"; // result is NO //NSString *value = @"BookName"; BOOL result = [person validateValue:&value forKey:@"name" error:&error]; if (result) { NSLog(@"OK"); } else { NSLog(@"NO"); }}Copy the code

7 Exception Handling

The key doesn’t exist

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {NSLog(@" The key you set: [%@] does not exist ", key); NSLog(@" you set value to: [%@]", value); } - (id)valueForUndefinedKey:(NSString *)key {NSLog(@" The key you accessed :[%@] does not exist ", key); return nil; }Copy the code

The value is nil

- (void)setNilValueForKey:(NSString *)key {// Handle attributes that cannot accept nil if ([key isEqualToString:@"price"]) {// Handle price for your business = 0; }else { [super setNilValueForKey:key]; }}Copy the code

Two: the underlying principle analysis

1 Principle of setting values

The default implementation of the setValue:forKey method will perform the following pattern search on the object after the caller passes in the key and value(if not the object type, the unwrapped value) :

  • 1. Search for such methods in the order set

    :, _set

    . If found, pass the attribute value to the method to complete the setting.

  • 2. The judgment methods accessInstanceVariablesDirectly results

    • If the returnYES, the_<key>._is<Key>.<key>.is<Key>If found, the property value is passed to the method to complete the property value setting.
    • If the returnNO, go to Step 3
  • 3. Call setValue: forUndefinedKey:. By default, this throws an exception, but subclasses of NSObject can provide key-specific behavior.

2 Value Mechanism

ValueForKey: The method searches the object for patterns after the caller passes in the key as follows:

  • 1. Check whether methods exist on objects in the order of get

    ,

    , is

    , and _< Key>.


    • If found, attach the method return value and jump to step 5
    • If not, go to step 2
  • 2. Look for countOf

    and objectIn

    AtIndex: methods (corresponding to the original method defined by the NSArray class) and

    AtIndexes: Method (corresponding to the 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
  • 3. Find three methods named countOf

    , enumeratorOf

    , and memberOf

    (corresponding to the original method defined by the NSSet class)


    • If all three methods are found, a response all is createdNSSetMethod, and returns the object
    • If not, go to Step 4
  • 4. The judgment methods accessInstanceVariablesDirectly results

    • If the returnYES, the_<key>._is<Key>.<key>.is<Key>Search for member variables in the order of, if found, add the member variable jump to step 5, if not, jump to step 6
    • If the returnNO, go to step 6
  • 5. Determine the value of the removed attribute

    • If the property value is an object, return it directly
    • If the property value is not an object, but can be converted toNSNumberType, the attribute value is converted toNSNumberType returns
    • If the property value is not an object, it cannot be converted toNSNumberType, the attribute value is converted toNSValueType returns
  • 6. Call valueForUndefinedKey:. By default, this throws an exception, but subclasses of NSObject can provide key-specific behavior.

Three:

Dynamic Settings and values

The use of KVC dynamic values and set values is the most basic use

Accessing private properties

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

Dictionary model

Works with runtimesetValuesForKeysWithDictionaryImplement dictionary to model.

Access internal properties of the control

For example 🌰, use KVC to change the textField placeholder text color and font size

[textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"]; 
[textField setValue:[UIFont boldSystemFontOfSize:16] forKeyPath:@"_placeholderLabel.font"];
Copy the code

The messaging

An 🌰

NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC"]; NSArray *lenStr= [array valueForKeyPath:@"length"]; NSLog(@"%@",lenStr); // The message is passed from array to string NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"]; NSLog(@"%@",lowStr); 🌹 printing results: Beijing, Shanghai, through, shenzhenCopy the code

In this example, all elements in the array are lowercase, and valueForKey passes the message to each element, not the container itself.