preface

KVC, also known as key-value Coding, is a common technical point in iOS development. It is believed that many developers have used KVC. The two main methods of KVC are as follows, respectively corresponding to setting Value and Value:

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

Usual development, we have a lot of places to use KVC, such as to an object attribute assignment, StroyBoard to control the layer set border and so on are THE application of KVC, this article will explore the principle of KVC and the use of precautions.

First, the use of KVC skills

1.1. KVC mechanism

Call KVC in the code, then go to the source code definition, and you can see that Apple has added a # NSKeyValueCoding category to NSObject, NSArray, and so on, as shown in the figure below:

So what is NSKeyValueCoding? Through the KVC documentation and NSKeyValueCoding, it can be found that the explanation is as follows:

NSKeyValueCoding is an informal protocol that provides a mechanism for indirectly accessing object properties through a clean string interface. This mechanism complements access to the object’s instance variables.

What to make of this passage? Let’s create a new project KVCDemo, then create a BPPerson class, and add the properties and member variables as follows:

When bpName, title, and hobby are called, the following occurs:

The bpName is defined in the.m implementation file, so it is not directly accessible from outside. In fact, if Hobby does not add @public, it defaults to protect, which is also not accessible. However, these limitations can be avoided with KVC as follows:

void personKVC(void) {

    BPPerson *person = [BPPerson alloc];

// person.title = @" The Lion ";

// person->hobby = @"running and programming";

//    person->bpName = @"奔跑";

    [person setValue:@"BP" forKey:@"bpName"];

    [person setValue:@"running and programming" forKey:@"hobby"];

    [person setValue:@"Siege Lion" forKey:@"title"];

    [person printPerson];
    [person printPersonKVC];
}
Copy the code

The code runs as follows

You can see that attributes and member variables are successfully accessed through KVC and that values and values are successfully assigned.

1.2 Introduction to KVC API

1.2.1 NSObject API

The two most basic apis in KVC are setValue: forKey: and valueForKey:, which set and fetch values by Key, respectively, as shown in the above example. In addition, there are a number of other methods for us to call, and the following methods will be explored as examples.”

The first method is as follows:

/* Given a key that identifies an _ordered_ to-many relationship, return a mutable array that provides read-write access to the related objects. Objects added to the mutable array will become related to the receiver, and objects removed from the mutable array will become unrelated. */
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
Copy the code

If you call this method when an object contains a data variable, you get a mutable array. By modifying the elements of the array, the original array is changed, even if it is an immutable array. Let’s verify by adding an immutable group attribute to BPPerson

支那@property(* * * *nonatomic* *, * *copy* *)NSArray *array;
Copy the code

Make the following call and get the following result:

@[@”1″, @”2″, @”3″], can’t be modified, but when you call this method, you get a mutable array, and change the values in it, you see that the original array is changed to the result in the graph.

The second method is mainly for introducing keyPath, and it is related to both reading and writing, so it is introduced together:

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

Let’s create a new BPAnimal class and add a PET property to BPPerson as follows:

支那@interface支那BPPerson : NSObject {

    **@public支那NSString*hobby; } * *@property(* * * *nonatomic* *, * *copy* *)NSString*title; 支那@property(* * * *nonatomic* *, * *copy* *)NSArray*array; 支那@property(* * * *nonatomic* *, * *strong**) BPAnimal *pet; / / pet- (* *void**)printPerson; - (* *void**)printPersonKVC; 支那@end* * * *@interface支那BPAnimal : NSObject支那@property(* * * *nonatomic* *, * *copy* *)NSString *breed; / / varieties支那@end支那Copy the code

BPPerson adds a pet pet attribute. Pets have their own breed, so BPAnimal contains a breed attribute. The following is an attempt to write and value using keyPath. The code and results are as follows

As can be seen from the results in the figure, the value is successfully assigned through the pet. Breed keyPath. In addition, there are other apis that have not been explored yet, and those who are interested can continue to explore. Here, we explore the apis that are commonly used.

1.2.2 NSArray valueForKey

So in normal times, when we use NSArray, the most common thing we use is objectAtIndex, which is based on the index. NSArray and NSMutableArray also have valueForKey methods, as shown in the following code:

支那@interface支那NSArray<ObjectType> (NSKeyValueCoding)

/* Return an array containing the results of invoking -valueForKey: on each of the receiver's elements. The returned array will contain NSNull elements for each instance of -valueForKey: returning nil.*/- (* *id**)valueForKey:(NSString *)key;

/* Invoke -setValue:forKey: on each of the receiver's elements.*/- (* *void**)setValue:(**nullable* * * *id**)value forKey:(NSString *)key;
@end

Copy the code

As you can see from the comment, what valueForKey for an array does is call the valueForKey method on the elements in the array and return an array. You can verify this with the following demo:

According to the result, for the array call setValue:forKey: changes the original lion to the tiger, and the value is also taken out of the tiger, because the elements in the array have changed at this time.

Note that if the elements in the array are not of the same class, then calling this method risks a crash because some elements may not have the passed key, as shown in the following example:

When the last element change for a person, the person did not breed variables, so the collapse, and error setValue: forUndefinedKey:

1.2.3 Difference between setValue:forKey: and setValue:forKey: of NSDictionary

When setting values with a mutable dictionary, we often call setObjcet:forKey:, but if setValue:forKey: does not report an error, and the assignment is successful, what’s the difference? Let’s first look at the dictionary’s KVC definition, which looks like this:

As you can see from the comment, when calling setValue:forKey:, it is equivalent to calling setObjcet:forKey:, but if the value passed is nil, then -removeobjectforkey: is called, and valueForKey is called for the value: This is equivalent to calling -objectForKey:. Let’s use a demo to verify:

It can be found that the original value of BP is NP, and when passed 666 the first time, the change succeeds, but when passed nil the second time, the Key BP is directly removed. Another detail you can see here is that the variable dictionary has setValue:forKey: later, but the immutable dictionary does not have the setValue:forKey: declaration, indicating that KVC is still preserving the mutable and immutable nature of the dictionary.

Second, the call process of KVC

For the KVC call process, the corresponding description can be seen in KVC Fundamentals. Below, we divided into setValue and valueForKey two parts to analyze the call process.

2.1 setValue: forKey:

The following description can be found in the official document:

It can be summarized in three steps:

  • First of all, in accordance with theSet the < < Key > Key >, _set < Key >If it is found, it is called and assigned to complete the KVC process
  • If not, andaccessInstanceVariablesDirectlyMethod returns YES_<Key>, _is<Key>, <Key>, is<Key>If one of them is found, the value passed in is assigned and the KVC process is completed
  • If neither of the above steps is found, callsetValue:forUndefinedKey:Method and throw an exception.

Through a Demo to verify below, first of all, according to the rules define the corresponding member variables and methods, and will return to NO accessInstanceVariablesDirectly first. The code is as follows:

@interface BPPerson : NSObject {
    NSString *hobby;
    NSString *_hobby;
    NSString *ishobby;
    NSString *_ishobby;
}

+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

- (void)setHobby:(NSString *)hobby {
    NSLog(@"setHobby == %s", __func__);
}

- (void)_setHobby:(NSString *)hobby {
    NSLog(@"setHobby == %s", __func__);
}

Copy the code

1. At this time, the call result is as follows:

You can see that the setHobby: method was called first, and all four member variables are nil, because the setHobby: method does not assign.

Select * from ‘sethobby’ where ‘sethobby’ = ‘sethobby’;

You can see that when you call the _setHobby: method directly, all four member variables are still nil, indicating that no value was assigned.

3, continue to comment _setHobby: method, and view the accessInstanceVariablesDirectly NO and YES, respectively:

  • accessInstanceVariablesDirectlyNOThe result is as follows:

This exception is thrown directly setValue: forUndefinedKey:, four member variables will not, of course, the assignment.

  • accessInstanceVariablesDirectlyYESThe result is as follows:

At this point, we find that the member variable _hobby has been successfully assigned, the others are still nil, and the first variable is found in the order of the second rule.

  • Annotate the member variables in order, and print the following:

According to the printed results, it can be found that the search order for member variables is indeed as described in the document _

, _is

,

, is

. During the test, the member variables were written as ishobby and _ishobby, which eventually caused an exception to be thrown. The third item was also verified from the side, and an exception was thrown if it was not found.

Is

and _is

variables are considered for variable lookup. Here we can add setIs

and _setIs

call validation.

First add two methods as follows:

- (* *void**)setIsHobby:(NSString *)hobby {
    NSLog(@"setIsHobby == %s", **__func__** ); } - (* *void**)_setIsHobby:(NSString *)hobby {
    NSLog(@"_setIsHobby == %s", **__func__** );
}
Copy the code

After calling setValue:forKey:, the execution result is as follows:

Then annotate the setIsHobby: method and execute setValue:forKey: again. The result is as follows:

In fact, after the _setHobby: comment, we don’t look up the member variable immediately. Instead, we call setIsHobby:, but after the setIsHobby: comment, we don’t look up _setIsHobby: again, we assign to the member variable directly. This means that one more setIs

method will be found in the first step of the document, but _setIs

will not be called.

The KVC call process is summarized as follows:

2.2 valueForKey:

The description of valueForKey is as follows:

This description can also be divided into several processes:

  • 1, in accordance with theGet <Key>, <Key>, is<Key>, _< Key>If it is found, it is called and step 5 is performed.
  • 2, if not found in the previous step, and is an array type, call the array related method;
  • 3, if not found in the previous step, and is a collection type, call the collection related method;
  • 4. If none of the above is found, judgeaccessInstanceVariablesDirectlyWhether it isYESIf forYES, the member variables are searched in turn_<key>._is<Key>.<key>.is<Key>, if found, proceed to step 5, otherwise proceed to step 6
  • 5. The value is found and needs to be processed
    • If the retrieved value is an object pointer, the object is returned directly,
    • If it’s an NSNumber scalar, store it in an NSNumber and return,
    • If it’s not an NSNumber scalar, it’s stored in an NSValue and returned
  • 6. If 1 to 4 is not found, throw an exception

There is processing for arrays and collections, but not for dictionaries because, as mentioned earlier, the KVC for dictionaries actually calls its own setObject forKey and objectForKey methods and does not look up its own member variables and properties.

The following validates valueForKey: for common objects:

Assign different values to the member variables as required, and add the getIsHobby method as you implement the first step above. Let’s start validation:

1. Call valueForKey: directly, and the result is as follows:

As you can see from the results, the getHobby method is called directly and is not evaluated from a member variable.

2. GetHobby, isHobby, _hobby

The analysis results are as follows:

  • annotationgetHobbyAfter that, the results are shown directly invokedhobbyMethod, not taken from the variable
  • annotationhobbyAfter that, the results are shown directly invokedisHobbyMethod, not taken from the variable
  • annotationisHobbyAfter that, the results are shown directly invoked_hobbyMethod, not taken from the variable
  • annotation_hobbyAfter is not invokedgetIsHobbyand_isHobbyMethod, but directly from the variable_hobbyThe values

From this result, the search order for methods is indeed get

,

, is

, _< Key>, and no other methods are called

3, verify the value of the variable to find, _isHobby, _isHobby, hobby, isHobby

The result shows that the variables are called in the order _

, _is< key>,

, is< key>, and the exception valueForUndefinedKey: is thrown if they are not found.

One more detail during the test is that when the _hobby assignment statement is commented, but the _hobby definition is not commented, the result printed is as follows:

You can see that instead of looking for _isHobby because _hobby is nil, you’re looking for _isHobby, and that order doesn’t change depending on the value of the variable, and that’s also true when we setValue:forKey:.

The flowchart of valueForKey is as follows:

conclusion

In summary, this paper mainly introduces the related contents of KVC, mainly including the following points:

  • KVC provides a mechanism for indirectly accessing the member variables and attributes of an object (it’s up to anyone to decide whether it’s safe, whether this technique breaks the encapsulation of an object, etc.), but Apple provides such a mechanism and hasn’t eliminated it, so it makes sense to exist).
  • KVC writing values, will call the relevant set method first, and then in accessInstanceVariablesDirectly to YES cases, will assign the corresponding member variables
  • Corresponding values, will first invoke the get method, if not, the accessInstanceVariablesDirectly to YES cases, will take the value of a variable

That is the exploration of THE KVC API, and its call process, so this exploration will stop, in fact, there is also the exploration of custom KVC, but the custom KVC is longer, and needs to take into account the array, dictionary, collection, multithreading, etc., not included in this chapter. In fact, there are many great gods on the network to write very powerful, here put one of the DIS_KVC_KVO for your reference.