preface

In iOS development, the concept of KVC is often asked. What is KVC? What exactly can KVC do? In this article, WE will make a simple analysis of KVC, which stands for key-value Coding, as described in the official Apple Key-value Coding Guide.

Key-value encoding 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 by string arguments through a compact, uniform messaging interface. This indirect access mechanism complements the direct access provided by instance variables and their associated access methods.

We can use setValue:forKey: to set the value of an attribute, and valueForKey: to get the value of an attribute.

KVC compatible object

When objects inherit from NSObject (directly or indirectly), they are typically key-value encoded, both using the NSKeyValueCoding protocol and providing a default implementation for the base methods. Such an object allows other objects to do the following via a messaging interface:

  • Access properties of an object
  • Operation set properties
  • Invokes the collection operator on the collection object
  • Access non-object properties
  • Properties are accessed through keyPath

KVC sets values and values

So how does KVC set and value object properties? Let’s take a look at the process according to the official documentation.

setter

The default implementation of setValue:forKey: attempts to set a property named key as value(or, for non-object properties, an unwrapped version of value, as represented by non-object values) inside the object receiving the call, using the following procedure:

  1. Find the first name namedset<Key>:or_set<Key>Accessors, in that order. If it is found, it is called with the input value (or unwrapped value as needed) and done.
  2. If no simple accessor is found, and if the class methodaccessinstancevariablesdirectreturnYES, find an instance variable whose name in order is_<key>,_is<Key>,<key>oris<Key>. If found, set the variable directly with the input value (or unwrapped value) and finish.
  3. Called when no accessor or instance variable is foundsetValue:forUndefinedKey:. By default, this throws an exception, butNSObjectA subclass of may provide behavior that specifies Key.

Let’s take a look at the KVC setter process with a simple example of actual code debugging to verify.

Setter sample code

Create a class ATPerson in the project, add two methods to implement setName: and _setName:, and add print information to the implementation. Call setValue:forKey: in the ViewController to see the print result, the code is as follows:

ViewController.m

#import "ViewController.h"
#import "ATPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    ATPerson *p = [[ATPerson alloc] init];
    [p setValue:@"Atom" forKey:@"name"];
}
@end
Copy the code

ATPerson.m

#import "ATPerson.h"

@implementation ATPerson

- (void)setName:(NSString *)name {
    NSLog(@"%s -- %@", __func__, name);
}

- (void)_setName:(NSString *)name {
    NSLog(@"%s -- %@", __func__, name);
}

@end
Copy the code

Run the code and output the result-[ATPerson setName:] -- Atom, the implementation of thesetName:Methods.So let’s takesetName:Comment it out, run it again, and you can see the execution_setName:Methods.The result can be verified by the above codesetterNext, let’s verify clause 2. inATPerson.hIncrement the corresponding four instance variables, and then in.mAdd to fileaccessinstancevariablesdirectFunction and returnYES.

ATPerson.h

#import <Foundation/Foundation.h>

@interface ATPerson : NSObject {
    @public
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}

@end
Copy the code

ATPerson.m

#import "ATPerson.h"

@implementation ATPerson

+ (BOOL)accessInstanceVariablesDirectly {
    return YES;
}

//- (void)setName:(NSString *)name {
//    NSLog(@"%s -- %@", __func__, name);
//}

//- (void)_setName:(NSString *)name {
//    NSLog(@"%s -- %@", __func__, name);
//}

@end
Copy the code

Add print information to viewController.m

- (void)viewDidLoad { [super viewDidLoad]; ATPerson *p = [[ATPerson alloc] init]; [p setValue:@"Atom" forKey:@"name"]; NSLog (@ "value - _name = % @, _isName = % @, name = % @, isName = % @", p - > _name, p - > _isName, p - > name, p - > isName); }Copy the code

Run the output again and you can see_nameSet the value, everything else isnull.Comment out the_nameRun it again and you can see_isNameThe value is set. This verifies the statement in article 2.Given these instances of instance variables, we can guess whether setter methods also existsetIsNameand_setIsNameAnd in the sameATPerson.mAdd these two methods in. runThe outputsetIsName:Next comment it outsetIsName:Only one_setIsName:Run it again and find no output, which leads to the conclusion that there is actually one more method in the first apple official 2 methodssetIsName:It will also be executed. If neither of the above is implemented, go to the third process, callsetValue:forUndefinedKey:And throws an exception.

So that’s the analysis flow of the setter in KVC.

getter

The default implementation of valueForKey:, given a key argument as input, performs the following internal operations from the class instance that receives valueForKey:

  1. Search for the first name in the instance asget<Key>,<key>,is<Key>or_<key>Accessor method of. If it is found, it is called and the results are used to proceed to Step 5. Otherwise, go to the next step.
  2. If no simple accessor method is found, search the instance for a matching patterncountOf<Key>andobjectIn<Key>AtIndex:(corresponding to theNSArrayThe original method of the class definition) and<key>AtIndexes:(corresponding to theNSArraymethodsobjectsAtIndexes:).
  • If the first and at least one of the other two are found, create a collection proxy object that responds to all NSArray methods and returns it. Otherwise, go to Step 3.
  • The proxy object then converts all NSArray messages it receives tocountOf<Key>.objectIn<Key>AtIndex:, and<key>AtIndexes:A combination of messages and converts it to the object that created it according to the key-value encoding. If the original object also implements a name calledget<Key>:range:The proxy object will also use this method when appropriate. In effect, the proxy object works with an object that conforms to the key-value encoding, allowing the underlying property to behave like NSArray even if it is not.
  1. If no simple access method or array access method group is found, the search is calledcountOf<Key>.enumeratorOf<Key>, andmemberOf<Key>:Three methods corresponding to the base method defined by the NSSet class.
  • If all three methods are found, create a collection proxy object that responds to all NSSet methods and returns it. Otherwise, go to Step 4.
  • The proxy object then converts any NSSet messages it receives tocountOf<Key>.enumeratorOf<Key>.memberOf<Key>:A combination of messages and transforms it into the object that created it. In effect, the proxy object works with objects that conform to the key-value encoding, allowing the underlying property to behave as if it were an NSSet, even though it is not.
  1. If no simple access method or set of collection access methods is found, and if the receiver’s class methodaccessinstancevariablesdirectreturnYES, search for instance variables_<key>._is<Key>.<key>oris<Key>, if found, directly obtain the value of the instance variable, and proceed to Step 5. Otherwise, go to Step 6.
  2. If the retrieved property value is an object pointer, simply return the result. If the value isNSNumberSupported scalar types are stored inNSNumberInstance and return the instance. If the result isNSNumberA scalar type that is not supported is converted toNSValueObject and returns the object.
  3. Call if all other methods failvalueForUndefinedKey:. By default, this throws an exception, butNSObjectA subclass of may provide key-specific behavior.
Getter sample code

Also add the following method implementation to atPerson.m in the project to validate the output of the getter.

- (NSString *)getName {
    return NSStringFromSelector(_cmd);
}

- (NSString *)name {
    return NSStringFromSelector(_cmd);
}

- (NSString *)isName {
    return NSStringFromSelector(_cmd);
}

- (NSString *)_name {
    return NSStringFromSelector(_cmd);
}
Copy the code

Then in viewController.m, the instance variable is assigned to the KVC value to print it out.

- (void)viewDidLoad { [super viewDidLoad]; ATPerson *p = [[ATPerson alloc] init]; // [p setValue:@"Atom" forKey:@"name"]; / / NSLog (@ "value - _name = % @, _isName = % @, name = % @, isName = % @", p - > _name, p - > _isName, p - > name, p - > isName); // KVC process p->_name = @"_name"; p->_isName = @"_isName"; p->name = @"name"; p->isName = @"isName"; NSLog(@" value -- %@", [p valueForKey:@"name"]); }Copy the code

Run the code and see the outputgetName.ATPerson.mthegetNameMethod comment out and run again, outputname.And so on, and then you goisNameand_nameMethod call. This validates step 1, and KVC’s getter followsget<Key>,<key>,is<Key>or_<key>To find the corresponding implementation method in turn, there is a return. If step 1 is not achieved, the following steps will be followed, and if it is not achieved, it will be reachedvalueForUndefinedKey:And throws an exception. The next steps will not be verified one by one due to space problems.

KVC customization

According to the official documentation of KVC, we have verified the flow of setter and getter and understood its principle. Can we implement a class similar to KVC by ourselves?

Design ideas

  1. judgekeyWhether the value is null
  2. Determine if there isset<Key>or_set<Key>methods
  3. Get the relevant instance variables
    • judgeaccessInstanceVariablesDirectlyWhether to returnYES
    • Determine if there is_<key>,_is<Key>,<key>oris<Key>And assign a value to the instance variable
  4. If none exists, raise an exception

The above is the design process of setter method, following the official steps of Apple, getter is similar, too, too much code will not be posted here, interested students can go to the gitee warehouse address.

conclusion

The above is the analysis of the principle of KVC in iOS. Although Apple does not provide the source code for KVC, it can be analyzed with the help of official documentation and verified by generation examples. In development, only through continuous exploration and analysis can we understand the underlying design ideas, to understand and apply them in our actual project development.