preface

An overview,

The full name of KVC is Key Value Coding, which can be interpreted as assigning and accessing Value through the name of an object’s attribute (Key). Without calling an explicit access method. This allows objects’ properties to be accessed and modified dynamically at run time, rather than determined at compile time.

The advantage of KVC is that in classes that do not have accessor (setter, getter) methods, point syntax is not available.

KVC provides a mechanism for indirectly accessing its attribute methods or member variables through strings.

Second, the use of

KVC is defined as an extension of NSObject, and there’s an explicit NSKeyValueCoding class name in Objective-C, so you can use KVC for any type that inherits NSObject.

Handling KVC in Swift versus Objective-C is slightly different. For example, all classes in Objective-C inherit from NSObject, but Swift doesn’t, so we need to explicitly declare inherit from NSObject in Swift.

Because of the Optional mechanism in Swift, valueForKey returns an Optional value. We need to unpack the value to get the actual value.

1. Basic use

Here are some of the most common methods in KVC:

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

For example, the Book class has the title name attribute as well as author, and the Author class has an Address attribute.

@interface Author : NSObject @property(strong, nonatomic) NSString* address; @end @implementation Author @end @interface Book : NSObject @property(strong, nonatomic) NSString* name; // title @property(strong, nonatomic) Author* Author; Author @end@implementation Book @endCopy the code

When using:

//  ViewController.m
#import "ViewController.h"
#import "Book.h"

@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Book *book = [[Book alloc]init];
    [book setValue:@"Hello world" forKey:@"name"]; // set the value NSLog(@"age=%@",[book valueForKey:@"name"]);  // 取值
    
    // keyPath 方式
    [book setValue:@"ShangHai" forKeyPath:@"author.address"]; // set the value NSLog(@"author.address=%@",[book valueForKeyPath:@"author.address"]); // Value} @endCopy the code

2. Underlying execution mechanism

  • [object setValue: @ “value” forKey: @ “property”]
  1. Accessor (setter method) matching: first look for a method with the same name as setKey. Find the direct assignment. [the self setProperty: @ “value”].

  2. Instance variable matching: Find instance variables with the same name as key, _isKey, _key, and isKey, and assign them directly. Property = value or _property = value.

  3. Can’t find, can direct error setValue: forUndefinedKey: can’t find the mistake.

If we want to make this class disable KVC, then rewrite the + (BOOL) accessInstanceVariablesDirectly method to return NO, so if the KVC had not found the set: the property name, can directly use setValue: forUNdefinedKey: Methods.

+ (BOOL)accessInstanceVariablesDirectly; If Set<Key> is not found, members will be searched in the order _key, _iskey, Key, and iskeyCopy the code
  • [object valueForKey: @ “property”]
  1. Accessor (getter method) matching: first look for methods with the same name as key, isKey, getKey (and _key).

  2. Instance variable matching: Find instance variables with the same name as key, _key, isKey, _isKey.

  3. If not, call valueForUndefinedKey:.

If value is BOOL or Int, KVC can automatically package or unpack the data of value or structure type into NSNumber or NSValue object for adaptation purpose.

Struct struct Book {var name:String} var Book = Book(name:"Swift") / /set
book[keyPath: name] = "swift4"
// get
let valueOfName = book[keyPath:name]

Copy the code

3. Key value verification

In real development we get special requirements for some Key values. KVC provides us with a way to verify that the Value corresponding to the Key is available:

- (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;
Copy the code

This gives us a chance to correct our mistakes. However, KVC does not automatically invoke key-value validation, which means that if we want key-value validation we need to do it manually. -(BOOL)-validate:error:, returns Yes by default.

For example:

@implementation Book
- (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];
    
    Book *book = [[Book alloc]init];
    NSError* error;
    NSString *value = @"Not-name"; // result is NO //NSString *value = @"BookName"; BOOL result = [book validateValue:&valueforKey:@"name" error:&error];
    if (result) {
        NSLog(@"OK");
    } else {
        NSLog(@"NO"); }}Copy the code

4. Handle non-existent key and nil values

  • Handle nonexistent key values

We can consider overriding setValue: forUndefinedKey: method and valueForUndefinedKey: method

- (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(@"Your access key:[%@] does not exist", key);
    return nil;

}
Copy the code
  • Handling of value nil

When a program tries to set a nil value for a property that does not accept a nil value, the program automatically executes the setNilValueForKey: method for that object. We can also override this method:

- (void)setNilValueForKey:(NSString *)key {// handles attributes that cannot accept nilif ([key isEqualToString:@"price"Price = 0; }else {
        [super setNilValueForKey:key]; }}Copy the code

5. Some function operations

KVC also provides some more complex functions, including the following:

  • Set operator

There are currently five functions: @avg, @count, @max, @min, and @sum5

  • Object operator

There are two of them: @distinctUnionofObjects and @UnionofObjects

They all return NSArray. The difference between the two is that the former removes duplicate data from the returned value, while the latter returns all data.

For example:

@interface Book : NSObject @property(assign, nonatomic) NSInteger price; @end @implementation Book @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Book *book1 = [Book new]; book1.price = 40; Book *book2 = [Book new]; book2.price = 20; Book *book3 = [Book new]; book3.price = 30; Book *book4 = [Book new]; book4.price = 10; // Book *book5 = [Book new]; book5.price = 10; NSLog(@"---------- set operator ----------");
    NSArray* arr = @[book1,book2,book3,book4];
    NSNumber* sum = [arr valueForKeyPath:@"@sum.price"];
    NSLog(@"sum:%f",sum.floatValue);
    NSNumber* avg = [arr valueForKeyPath:@"@avg.price"];
    NSLog(@"avg:%f",avg.floatValue);
    NSNumber* count = [arr valueForKeyPath:@"@count"];
    NSLog(@"count:%f",count.floatValue);
    NSNumber* min = [arr valueForKeyPath:@"@min.price"];
    NSLog(@"min:%f",min.floatValue);
    NSNumber* max = [arr valueForKeyPath:@"@max.price"];
    NSLog(@"max:%f",max.floatValue);
    
    NSLog(@"---------- object operator ----------");
    NSArray* arrBooks = @[book1,book2,book3,book4, book5];
    NSLog(@"distinctUnionOfObjects");
    NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
    for (NSNumber *price in arrDistinct) {
        NSLog(@"%f",price.floatValue);
    }
    NSLog(@"unionOfObjects");
    NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
    for (NSNumber *price in arrUnion) {
        NSLog(@"%f",price.floatValue); }} @end -- Output result: -- -- -- -- -- -- -- -- -- -- -- -- -- set operator -- -- -- -- -- -- -- -- -- - the sum, avg 100.000000:25.000000 the count: 4.000000 min: Max 10.000000:40.000000 ---------- object operator ---------- distinctUnionOfObjects 10.000000 20.000000 30.000000 40.000000 unionOfObjects 40.000000 20.000000 30.000000 10.000000 10.000000Copy the code

Three, practical application

1. Access private variables

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

@interface Book : NSObject { NSString * owner; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Book *book = [Book new]; // This access mode will directly report an error. Because owner is a private property. // book.owner = @"tao"; // Use KVC to access private variable [book]setValue:@"tao" forKey:@"owner"];
    NSLog([book valueForKey:@"owner"]); // print tao} @endCopy the code

2, modify the internal properties of some controls

In the development, we often need to modify some properties of some controls, but a lot of UI controls are composed of a lot of internal UI controls, the system does not provide the API to access these controls, so we can not access and modify the style of these controls normally. However, KVC can help us solve most of these types of problems.

3. Combine Runtime to create dictionary to Model

Dictionaries can be transformed into models using KVC and the runtime. The specific code can be viewed on my Github.

Four,

The above points are some records of my study of KVC, if there is something wrong, please point out a lot. For more detailed information about KVC, you can go to the official documentation key-value Coding Programming Guide.