Blog post: 2019-3-14 The article was substantially reconstructed according to personal reading experience. Blog post: 2019-3-25

The introduction

This article was actually forced by his brother KVO. KVC is described in the official document as the basis for the implementation of KVO technology. Limited knowledge, there are wrong places, but also please point out more.

An overview of the

Key-value coding (KVC) is a mechanism enabled by the NSKeyValueCoding informal protocol (actually what we call classification or category) that objects use to provide indirect access to their attributes. When an object conforms to a key-value encoding, its properties can be addressed using string arguments through a concise, uniform messaging interface (method). This indirect access mechanism complements the direct access provided by instance variables and their associated accessor methods.

Key-value encoding is a fundamental concept that underlies many other Cocoa technologies, such as KVO, (macOS) Cocoa binding, Core Data, and AppleScript. In some cases, key-value coding also helps simplify code.

Here we have a very official description, but in a nutshell, we are accessing object properties by string names, and that’s it.

API interface

Common usage

Accessing object properties

- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
Copy the code

KVC provides a concise, unified way to access object properties. ValueForKey :for getter accessors and setValue:forKey: for setter accessors. Fortunately, NSObject adopts the NSKeyValueCoding protocol and provides a default implementation for them and other basic methods. So if you derive objects from NSObject (or any of its many subclasses), most of the work is already done.

@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end

@interface Person : NSObject
@property (nonatomic.copy) NSString *name;
@property (nonatomic) NSUInteger age;
@end
Copy the code

Now that we have declared two classes to illustrate the basic usage of KVC, we assume that the instance object of BankAccount is myAccount, and we normally operate on properties directly using accessor methods.

myAccount.currentBalance = @(100.0);
/ / or
[myAccount setCurrentBalance:@(100.0)];
Copy the code

Of course, we know that these two methods are equivalent. Now let’s look at how KVC is used:

// setter
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
// getter
NSNumber *currentBalance = [myAccount valueForKey:@"currentBalance"];
Copy the code

Key path access property

If we want to get the name of the head of the bank account, we can easily get it using the dot syntax after importing Person.h:

NSString *myName = myAccount.owner.name;
Copy the code

Of course, KVC also provides a way for us to access properties of properties through key paths. A key path is a dot separated string of keys used to specify the sequence of object properties to traverse. The first key in the sequence is a property relative to the receiver, and each subsequent key is a property relative to the previous key.

- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
Copy the code

Now we can access properties using key paths:

NSString *myName = [myAccount valueForKeyPath:@"owner.name"];
[myAccount setValue:@"SepCode" forKeyPath:@"owner.name"];
Copy the code

Key undefined exception

According to the regulations of KVC (search pattern) cannot find a named by the key attributes, is called for value valueForUndefinedKey: or set the value setValue: forUndefinedKey: method, By default, this method raises an NSUndefinedKeyException that causes a crash. We can override this method to avoid a crash. And we can rewrite the method to add logic to make it more elegant.

// Override UndefinedKey: method
// getter
- (id)valueForUndefinedKey:(NSString *)key {
    return nil;
}
// setter
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    
}
Copy the code

Non-object values and nil

When we assign a value to a property by setValue:forKey:, if the property is not an object but a scalar or structure, KVC will automatically expand the object to get the value and assign it to the property. Also, when valueForKey: is executed, the attribute value is wrapped automatically, returning an NSNumber or NSValue object corresponding to it.

// setter
[owner setValue:@(26) forKey:@"age"];
// getter
NSNumber *myAge = [owner valueForKey:@"age"];
Copy the code

When we assign nil to an object, it’s easy to understand that we’re setting the object to null. But when we set non-object values to nil by setValue:forKey:, there are no objects to expand, do we all set these non-object values to zero? Officials did not give us the assignment operation, realize the default but call setNilValueForKey: method, and the method of the system default will trigger a NSInvalidArgumentException exceptions, of course, we also can override this method to realize a specific behavior.

// nil
[owner setValue:nil forKey:@"age"]; . - (void)setNilValueForKey:(NSString *)key {
    if ([key isEqualToString:@"age"{[])self setValue:@(0) forKey: @ "age"]; }else{[supersetNilValueForKey:key]; }}Copy the code

Multivalued access

We see dictionary-specific methods also provided, but they are not dictionary-specific methods. It’s a method that accesses multiple properties at the same time, which is essentially calling setValue:forKey: or valueForKey: methods on each key, which is pretty easy to understand and we won’t go over.

NSDictionary *dict = [owner dictionaryWithValuesForKeys:@[@"name".@"age"]];
dict = @{@"name":@"sepCode".@"age": @ (62)};
[owner setValuesForKeysWithDictionary:dict];
Copy the code

Special usage

Accessing collection properties

We talked earlier about how KVC accesses objects, but it also applies to collection objects. You can get or set collection objects just like any other object by using valueForKey: and setValue:forKey: (or their key paths).

@interface Transaction : NSObject
 
@property (nonatomic) NSString* payee;   // To whom
@property (nonatomic) NSNumber* amount;  // How much
@property (nonatomic) NSDate* date;      // When
 
@end
Copy the code

Now we define a transaction class, if we want to get all the payees in an individual’s bank account.

NSArray *payees = [myAccount valueForKeyPath:@"transactions.payee"];
Copy the code

Requesting the value of the transactions.payee key path returns an array containing all payee objects for transactions. This also applies to multiple arrays in key paths. If we want to get multiple bank accounts of all the payee, request key path accounts. The transactions. The value of the payee returns an array containing all the payee object of all transactions of all accounts.

We see the convenience of KVC for getting values, but we rarely use KVC for setting values. It sets the values of all the key objects contained in the collection to the same value, which is not what we want.

Although there are generic ways to access collection objects, when you want to manipulate the contents of these collections, the official recommendation is that we use the protocol-defined variable proxy method as the most effective method. Agreement for access to the collection objects defines three different agent method, each method has the key and variants keyPath: mutableArrayValueForKey: and mutableArrayValueForKeyPath: They return a proxy object that behaves like the NSMutableArray object. MutableSetValueForKey: mutableSetValueForKeyPath: and they returned a behavior similar to NSMutableSet object proxy objects. MutableOrderedSetValueForKey: mutableOrderedSetValueForKeyPath: and they returned a behavior similar to NSMutableOrderedSet object proxy objects.

When you operate on a proxy object, add objects, delete objects from it, or replace objects, the default implementation of the protocol modifies the original object accordingly. Now suppose we want to add a transaction to a personal bank account using the KVC generic method, get a non-mutable collection object with valueForKey, create a mutable collection object to add content, and then store it back to the object using the setValue:forKey: message. By contrast, it is much more convenient to operate through proxy objects. In many cases, it is more efficient than using mutable attributes directly. For example, when we use variables instead of constant strings as keys. This allows us not to know the exact name of the method being called, as long as the object and the key being used conform to KVC, everything will work fine. These methods also enable key-value observation when maintaining objects in collections. That’s why, halfway through the KVO article, I suddenly started writing about KVC first.

The important thing to note here is that these methods return a proxy object for a collection object. Of course, you can return a property collection object by requesting properties of objects in the collection as we talked about earlier, but this is limited to getting values. If we manipulate the property collection object in this case, the value of the property of the object in the original collection will be set to the property collection object after the operation, which is also not the desired result.

Use the set operator

You can embed the set operator in the key path when you send a valueForKeyPath: message to an object that matches the key encoding, or when the object calls the valueForKeyPath: method. The collection operator is a keyword preceded by the AT symbol (@) that specifies what the getter should do to manipulate the data in some way before returning it. NSObject provides a default implementation for this behavior.

When the key path contains a collection operator, the key path that precedes the operator (called the left key path) indicates the collection of operations relative to the message receiver. If you send a message directly to a collection object (such as an NSArray instance), you can omit the left-key path. The key path portion (called the right-click path) after the operator specifies the properties in the collection that the operator should work with. All set operators except @count require right-click paths.

The behavior of set operators can be divided into three basic types:

  • The aggregate operator somehow merges the objects of a collection and returns a single object that usually matches the data type of the property specified in the right-click path. @count is an exception, it doesn’t have a right-click path and if it does it will be ignored and will always return an NSNumber instance.

    NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
    NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];
    NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];
    Copy the code
  • The array operator returns an array of objects corresponding to a particular set of objects indicated by the right-click path.

  • The nesting operator processes collections containing other collections and, depending on the operator, returns an instance of NSArray or NSSet that somehow combines the objects of the nested collection.

For details on how to use the operator, please click on the above hyperlinks in the official documentation.

Property verification

The key-value encoding protocol defines methods to support attribute validation. Just as with the KVC generic approach, you can also keypress (or keypath) to verify properties. When you call validateValue: forKey: error: (or validateValue: forKeyPath: error:) method, the default implementation of the agreement will make the search object instance is realized the validate < Key > : error: method. If the object does not implement such a method, the validation succeeds by default and YES is returned.

Generally, the following authentication methods can be used:

  • When the value object is valid, return YES without changing the value object or error.

  • When the value object is invalid and you cannot or do not want to provide a valid alternative method, set the error cause NSError and return NO.

  • When a value object is invalid but you know a valid alternative, create a valid object, assign the value reference to the new object, and return YES without setting an NSError error. If other values are provided, the new object is always returned, rather than modifying the object being validated, even if the original object is mutable.

Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if(! [person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@ "% @",error); }... - (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
    if ((*ioValue == nil) | | ([(NSString *)*ioValue length] < 2)) {
        if(outError ! =NULL) {
            *outError = [NSError errorWithDomain:PersonErrorDomain
                                            code:PersonInvalidNameCode
                                        userInfo:@{ NSLocalizedDescriptionKey
                                                    : @"Name too short" }];
        }
        return NO;
    }
    return YES;
}
Copy the code

The above use case demonstrates a validation method for a name string property that ensures that the value object has a minimum length and is not nil. This method does not replace other values if validation fails.

The principle of analytic

Visitor search pattern

The most critical part of the KVC protocol is the visitor search pattern, the default implementation of the NSKeyValueCoding protocol provided by NSObject, which uses a well-defined set of rules to map key-based accessor (KVC access method) calls to properties of objects. These protocol methods use key arguments to search for accessors, instance variables, and related methods in their own object instances that follow certain naming conventions.

The search pattern for mutable arrays

Here we describe only one search pattern for a schema variable array. The other search patterns can be learned in detail through the visitor search pattern.

The default implementation of mutableArrayValueForKey: returns a mutable proxy array by entering a key argument. The property inside the object, named key, receives calls from accessors through the following procedure:

  1. Find a pair of method names like insertObject:in

    AtIndex: and removeObjectFrom

    AtIndex: :(corresponding to NSMutableArray’s base methods insertObject: AtIndex: and removeO, respectively BjectAtIndex: : or methods with names similar to INSERT

    :atIndexes: and remove

    atIndexes: (insertObjects:atIndexes: and removeObj corresponding to NSMutableArray EctsAtIndexes: Methods).



    If the object has at least one insert method and at least one delete method, return a proxy object in response to these NSMutableArray messages. By sending some combination of messages insertObject:in

    AtIndex:, removeObjectFrom

    AtIndex:, Insert

    :atIndexes:, and remove

    atIndexes: to mutableArrayValueForKey: recipients of the message. Or it can be expressed as responding to these insert or delete methods by calling the above method on the object that called the mutableArrayValueForKey: method.



    When the object receiving the mutableArrayValueForKey: message also implements an (optional) replacement method named replaceObjectIn

    AtIndex:withObject: or replace

    AtIndexes: With

    : Proxy objects also use these methods when appropriate for best performance.


  2. If the object does not have a mutable array method, look for accessor methods of set

    : whose name matches the pattern set. In this case, a proxy object is returned. Respond to those NSMutableArray messages above by sending a set

    : message to the original recipient of mutableArrayValueForKey:.

    Note: The first two steps are simply to look for insert, delete, and (optionally) replace methods when the proxy object manipulates the contents of the collection, and to look for setter methods if they are not implemented. The mechanism described in Step 2 is much less efficient than the previous step because it may involve repeatedly creating new collection objects rather than modifying existing ones. Therefore, you should generally avoid using it when designing your own key-value encoding-compliant objects.

  3. If neither find variable array method, also the visitor was not found, and the receiver’s class of accessInstanceVariablesDirectly response to YES, said allows you to search an instance variable, the sequential search name is _ < key > or < key > instance variables. If such an instance variable is found, a proxy object is returned that forwards each NSMutableArray message it receives to the instance variable, typically an instance of NSMutableArray or one of its subclasses.

  4. If all the other methods have failed, it returns a mutable collection agent object, the object to mutableArrayValueForKey when received NSMutableArray news: the message of the original recipient setValue: forUndefinedKey: message. SetValue: forUndefinedKey: the default implementation of trigger NSUndefinedKeyException anomalies.

    Note: The last two steps simply say, if you are allowed to search for instance variables, search for variables, and if all the above searches fail, an error is reported.

The principle of practice

Now let’s do some practice and test according to the search pattern of mutable array:

@interface ViewController(a)
/// array
@property (nonatomic.strong) NSMutableArray *array;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.array = [@[@(1), @ (2), @ (3)] mutableCopy];
    NSMutableArray *kvcArray = [self mutableArrayValueForKey:@"array"];
    // Send the NSMutableArray message
    [kvcArray addObject:@(4)];
    [kvcArray removeLastObject];
    [kvcArray replaceObjectAtIndex:0 withObject:@(4)];
    
}
// Variable array many-to-many optimization
- (void)insertObject:(NSNumber *)object inArrayAtIndex:(NSUInteger)index {
    [self.array insertObject:object atIndex:index];
}

- (void)removeObjectFromArrayAtIndex:(NSUInteger)index {
    [self.array removeObjectAtIndex:index];
}

- (void)replaceObjectInArrayAtIndex:(NSUInteger)index withObject:(id)object {
    [self.array replaceObjectAtIndex:index withObject:object];
}

@end
Copy the code

The test results above show that if we use proxy objects, we better implement the full protocol and optimize the many-to-many relationship, otherwise the performance drops exponentially as the data magnitude increases, which is really bad.

Suspects to reassure

Here I would like to share my understanding that KVC is the foundation of the KVO implementation. Because I saw an author who wrote a good article on the Internet, he said that the two implementation mechanisms are different, there is no necessary connection, but KVC support for KVO is better. I couldn’t disagree more. The official key-value observation programming guide explicitly states that the properties of this class must comply with KVC compliance. KVC is a protocol for accessing object properties through strings, including search patterns, which are also part of the protocol. Properties observed by KVO must comply with KVC compliance and can be modified by observing all KVC-compliant accessors. KVO as we usually understand it is implemented based on setter accessors, but this is not the case. The following figure also fully verifies that KVO supports KVC search mode:

This reminds me of the advice of LAN Jiangang at Ele. me Tech Salon: Chinese blogs – read less until you have no ability to tell right from wrong.

conclusion

This article, write write I have feeling again. I deeply feel that I am a learner, and all these knowledge are created by others, using the methods provided to us by others. Even learning can be summed up by others. I am not a creator yet.

But there’s nothing wrong with recognizing how lame you are. Even in the same learning stage of others, can also become their own teachers, I hope you can give more guidance.

I’ve been reading a lot of other people’s articles lately, and I’ve found a few things from my own feelings.

  • I like the author to express the technology clearly through diagrams or words, I don’t like to see the author’s large sections of code to express, but simple use cases are still necessary.
  • Don’t list all the interfaces at once. At most glance, unless the author’s goal is for you. So the order of presentation can be statement, interface, use case. Point by point.
  • The structure of the article is clear, not one foot in the sky, one foot on the ground, so the premise is the author’s clear thinking.

You don’t need to read too many books. Read classic books several times and think for yourself. This article is basically born on the basis of reading more official documents, I still care about the details, if there is any misunderstanding, please correct me more.