preface

Key-value coding (KVC) is a name that may not be easy to understand. In iOS development, developers can use the Key name to directly access the properties of the object, or assign values to the properties of the object. Without calling an explicit access method. 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. Many advanced iOS development techniques are implemented based on KVC. At present, there are a lot of articles about KVC on the Internet. Some of them just say the usage in a simple way, and some of them go deep, but there is no explanation in the use scenarios and best practices. I write this article is to give you a most complete and detailed EXPLANATION of KVC.

KVC is defined in iOS

For both Swift and Objective-C, KVC is defined as an extension of NSObject (Objective-C has an explicit NSKeyValueCoding class name, which Swift does not and does not require). So for all types that inherit NSObject, which means that almost all Objective-C objects can use KVC(some pure Swift classes and constructs don’t support KVC), here are the four most important methods of KVC

- (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 KeyPath. There are other methods in the NSKeyValueCoding category, some of which are listed belowCopy the code

Of course, there are other methods in the NSKeyValueCoding category, so here are some of them

+ (BOOL)accessInstanceVariablesDirectly; // Return YES by default, if the Set<Key> method is not found, members will be searched in _key, _iskey, Key, iskey order. - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError; //KVC provides an API for verifying the correctness of attribute values, which can be used to check if the value of a set is correct, make a replacement value for an incorrect value, or reject a new value and return the cause of the error. - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; // This is the set operation API, there are a series of such apis, if the property is an NSMutableArray, then you can use this method to return. - (nullable id)valueForUndefinedKey:(NSString *)key; // If the Key does not exist and no KVC can find any fields or attributes related to the Key, this method is called. By default, an exception is thrown. - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; // Same as the previous method, but this method is set. - (void)setNilValueForKey:(NSString *)key; // If you pass nil to Value in the SetValue method, Will call this method - (NSDictionary < nsstrings *, id > *) dictionaryWithValuesForKeys: (NSArray keys < > nsstrings * *); // Input a set of keys, return the values corresponding to the keys, and then return the dictionary, which is used to transfer the Model to the dictionary.Copy the code

These methods above will be used in special situations or special needs, so you can also understand C. Some of these methods are covered in future code examples. Also, Apple has a special implementation for some container classes like NSArray or NSSet, KVC. Developers with basic knowledge or good English are advised to go directly to apple’s official documentation, I believe you will have a better understanding of KVC.

Some readers may not know how to check official documents, here is an explanation. Open Xcode, look at the top menu, and click on the last Help -> Documentation and API Reference to open the official Documentation.

How did KVC find Key

I’m sure most developers are well aware of how KVC works. Instead of writing simple code that uses KVC to set and value values, let’s discuss the order in which KVC internally finds keys.

Set the value

When the setValue: forKey: @ “name” code is called, the underlying execution mechanism is as follows:

  • Program priority callSet < Key > : attribute valuesMethod, the code passessetterMethod to complete the setting.Note that the name of the member variable is in accordance with the KVC naming rules
  • If you don’t find itElegantly-named setName:Method, the KVC mechanism checks+ (BOOL)accessInstanceVariablesDirectlyMethod does not return YES. By default, this method does return YES. If you override the method to return NO, KVC will do so at this stepSetValue: forUndefinedKey:Method, but most developers don’t do that. So the KVC mechanism will search the class for names_<key>, regardless of whether the variable is defined at the interface of the class or at the implementation of the class, and regardless of what access modifier is used, only exists when_<key>KVC can assign a value to a named variable.
  • If the class has noneSet < key > :Method, no_<key>Member variables, KVC mechanism will search_is<Key>A member variable of.
  • As above, if the class has noneSet < Key > :Method, no_<key>and_is<Key>Member variables, KVC mechanism will continue to search<key>andis<Key>A member variable of. And I’m going to assign them.
  • If none of the methods or member variables listed above exist, the system will execute the objectSetValue: forUndefinedKey:Method, which by default throws an exception.

If developers want this class to disable the KVC, then rewrite the + (BOOL) accessInstanceVariablesDirectly method to return NO, so if the KVC had not found the set < Key > : the property name, can directly use setValue: ForUndefinedKey: method.

Let’s test the above KVC mechanism with code

@interface Dog : NSObject @end @implementation Dog { NSString* toSetName; NSString* isName; //NSString* name; NSString* _name; NSString* _isName; } // -(void)setName:(NSString*)name{ // toSetName = name; // } //-(NSString*)getName{ // return toSetName; //} +(BOOL)accessInstanceVariablesDirectly{ return NO; } -(id)valueForUndefinedKey:(NSString *)key{NSLog(@" exception occurred, the key does not exist %@",key); return nil; } -(void)setValue:(id)value forUndefinedKey:(NSString *)key{NSLog(@" error, the key does not exist %@",key); } @end int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... Dog* dog = [Dog new]; [dog setValue:@"newName" forKey:@"name"]; NSString* name = [dog valueForKey:@"toSetName"]; NSLog(@"%@",name); } return 0; }Copy the code

First of all, let’s rewrite accessInstanceVariablesDirectly ways to make its return NO, and then run the code (note that the above comments section), Xcode print out directly

2016-04-15 15:52:12.039 DemoKVC[9681:287627] is abnormal and the key does not exist. Name 2016-04-15 15:52:12.040 DemoKVC[9681:287627] is abnormal. ToSetName 2016-04-15 15:52:12.040 DemoKVC[9681:287627] (null)Copy the code

This illustrates the rewrite + (BOOL) accessInstanceVariablesDirectly ways to make its return after NO KVC can’t find the elegantly-named setName: method, don’t go looking for the member variable name series, but direct call setValue: forUndefinedKey: Method so developers who don’t want their classes to implement KVC can do so. I’m going to uncomment those two setters and getters down here, and I’m going to go

NSString* name = [dog valueForKey:@"toSetName"]; NSString* name = [dog valueForKey:@"name"];Copy the code

XCode will print out the correct values correctly

The 2016-04-15 15:56:22. 130 DemoKVC [9726-289258] newNameCopy the code

Are commented out below accessInstanceVariablesDirectly method, can test other key find order, to save space, the rest of KVC shows for key search mechanism is not here, interested readers can write code to test and verify.

The values

When calling valueForKey: @ “name” code, KVC searches forKey in a different way than setValue: forKey: @ “name”. The search method is as follows:

  • First of all, according to theget<Key>.<key>.is<Key>The sequential method lookup ofgetterMethod is called directly if found. If it isBOOLorIntThe equivalent type will be wrapped as aNSNumberObject.
  • If the topgetterIf it doesn’t, KVC looks for itcountOf<Key>.objectIn<Key>AtIndexor<Key>AtIndexesFormat methods. ifcountOf<Key>Method and one of the other two methods are found, and a ready response is returnedNSArrayA collection of proxies for all methods (it isNSKeyValueArray, it isNSArraySubclass), calls the methods of the proxy collection, or sends a class ofNSArray“, will be tocountOf<Key>.objectIn<Key>AtIndexor<Key>AtIndexesCall in the form of a combination of these methods. There’s another optionget<Key>:range:Methods. So if you want to redefine some of the functionality of KVC, you can add these methods. Be careful that your method names follow KVC’s standard naming methods, including method signatures.
  • If the above methods are not found, both are searchedcountOf<Key>.enumeratorOf<Key>.memberOf<Key>Format methods. If all three methods are found, a valid response is returnedNSSetThe proxy collection of all methods, as above, is sent to this proxy collectionNSSet“, will be withcountOf<Key>.enumeratorOf<Key>.memberOf<Key>Call in the form of a combination.

If you have a custom implementation of KVC in your own class, and implement the above methods, then congratulations, you can use the returned object as an array (NSArray), see the following example code for details

  • If not, check the class method again+ (BOOL)accessInstanceVariablesDirectlyIf YES(the default behavior) is returned, the same value as the previous setting is pressed_<key>,_is<Key>,<key>,is<Key>This is not recommended because direct access to instance variables breaks encapsulation and makes the code more vulnerable. If you override the class method+ (BOOL)accessInstanceVariablesDirectlyIf you return NO, it will be called directlyvalueForUndefinedKey:
  • If not found, callvalueForUndefinedKey:

Now let’s test the code

@interface TwoTimesArray : NSObject -(void)incrementCount; -(NSUInteger)countOfNumbers; -(id)objectInNumbersAtIndex:(NSUInteger)index; @end @interface TwoTimesArray() @property (nonatomic,readwrite,assign) NSUInteger count; @property (nonatomic,copy) NSString* arrName; @end @implementation TwoTimesArray -(void)incrementCount{ self.count ++; } -(NSUInteger)countOfNumbers{ return self.count; -(id)objectInNumbersAtIndex:(NSUInteger)index{// KVC will find both methods when key uses numbers. return @(index * 2); } -(NSInteger)getNum{// return 10; } -(NSInteger)num{// second return 11; } -(NSInteger)isNum{// return 12; } @end int main(int argc, const char * argv[]) { @autoreleasepool { TwoTimesArray* arr = [TwoTimesArray new]; NSNumber* num = [arr valueForKey:@"num"]; NSLog(@"%@",num); id ar = [arr valueForKey:@"numbers"]; NSLog(@"%@",NSStringFromClass([ar class])); NSLog(@"0:%@ 1:%@ 2:%@ 3:%@",ar[0],ar[1],ar[2],ar[3]); [arr incrementCount]; //count + 1 NSLog(@"%lu",(unsigned long)[ar count]); // Print 1 [arr incrementCount]; NSLog(@"%lu",(unsigned long)[ar count]); // Print out 2 [arr setValue:@"newName" forKey:@"arrName"]; NSString* name = [arr valueForKey:@"arrName"]; NSLog(@"%@",name); } return 0; } // The result is displayed 2016-04-17 15:39:42.214 KVCDemo[1088:74481] 10 2016-04-17 15:39:42.215 KVCDemo[1088:74481] NSKeyValueArray -(id)objectInNumbersAtIndex:(NSUInteger)index; Methods 2016-04-17 15:39:42.215 KVCDemo[1088:74481] 1 2016-04-17 15:39:42.215 KVCDemo[1088:74481] 2 2016-04-17 15:39:42.215 KVCDemo[1088:74481] newNameCopy the code

Obviously, the above code fully illustrates the mechanism by which KVC searches for keys when ValueforKey: @ “name” is called. However, there are some features that are not completely listed, so interested readers can write code to verify them.

Use keyPath in KVC

However, in the development process, the member variables of a class may be a custom class or other complex data type. You can use KVC to obtain the attributes of the custom class first, and then use KVC to obtain the attributes of the custom class again, but this can be cumbersome. KVC provides a solution to this problem, which is the keyPath keyPath.

- (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
@interface Address : NSObject @end @interface Address() @property (nonatomic,copy)NSString* country; @end @implementation Address @end @interface People : NSObject @end @interface People() @property (nonatomic,copy) NSString* name; @property (nonatomic,strong) Address* address; @property (nonatomic,assign) NSInteger age; @end @implementation People @end int main(int argc, const char * argv[]) { @autoreleasepool { People* people1 = [People new]; Address* add = [Address new]; add.country = @"China"; people1.address = add; NSString* country1 = people1.address.country; NSString * country2 = [people1 valueForKeyPath:@"address.country"]; NSLog(@"country1:%@ country2:%@",country1,country2); [people1 setValue:@"USA" forKeyPath:@"address.country"]; country1 = people1.address.country; country2 = [people1 valueForKeyPath:@"address.country"]; NSLog(@"country1:%@ country2:%@",country1,country2); } return 0; } // Result 2016-04-17 15:55:22.487 KVCDemo[1190:82636] COUNTRY1 :China country2:China 2016-04-17 15:55:22.489 KVCDemo[1190:82636] country1:USA country2:USACopy the code

The above code briefly shows how keyPath is used. If you accidentally use key instead of keyPath, as in the above code, KVC will look directly for the address.country property, which obviously doesn’t exist, so it will call the undefinedKey method. KVC for keyPath is the search mechanism and the first step is to separate the key, use the decimal point. To split keys, then search for them in the same order as normal keys.

How does KVC handle exceptions

The most common exceptions in KVC are accidentally using the wrong key or accidentally passing a nil value in a set, and there are special methods in KVC to handle these exceptions.

Typically when working with a Model using KVC, the two methods that throw exceptions need to be overridden. Although it is rare to pass the wrong Key, if it does, it is not reasonable to throw an exception and crash the APP. Usually you can just print the key here, or there are special cases that require special processing. In general, KVC does not allow you to pass a nil value to a non-object when calling setValue: property value forKey: @ “name” (or keyPath). That’s easy, because value types can’t be nil. If you do, KVC will call setNilValueForKey:. This method throws exceptions by default, so it’s generally best to override this method.

[people1 setValue:nil forKey:@"age"] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<People 0x100200080> setNilValueForKey]: Could not set nil as the value for the key age.' // setNilValueForKey throws an exceptionCopy the code

If I rewrite setNilValueForKey:, I’m fine

@implementation People -(void)setNilValueForKey:(NSString *)key{NSLog(@" cannot set %@ to nil",key); } @end // prints 2016-04-17 16:19:55.298 KVCDemo[1304:92472] Cannot set age to nilCopy the code

KVC handles both non-objects and custom objects

Not every method returns an object, but valueForKey: always returns an ID object. If the original variable type is a value type or structure, the return value is encapsulated as an NSNumber or NSValue object. These two classes handle anything from numbers and booleans to Pointers and structs. The openers then need to manually convert to the original type. While valueForKey: automatically encapsulates value types as objects, setValue: forKey: does not. You have to manually convert the value type to NSNumber or NSValue to pass it through.

For custom objects, KVC also sets values and values correctly. Since the id type is passed in and out, the developer has to guarantee that the type is correct. The runtime Objective-C checks the type before sending the message and throws an exception if it is wrong.

Address* add2 = [Address new]; add2.country = @"England"; [people1 setValue:add2 forKey:@"address"]; NSString* country1 = people1.address.country; NSString * country2 = [people1 valueForKeyPath:@"address.country"]; NSLog(@"country1:%@ country2:%@",country1,country2); 2016-04-17 16:29:36.349 KVCDemo[1346:95910] country1:England Country2 :EnglandCopy the code

KVC and container classes

The attributes of an object can be one-to-one or one-to-many. One-to-many attributes are either ordered (arrays) or unordered (collections).

Immutable ordered container properties (NSArray) and unordered container properties (NSSet) can generally be obtained using valueForKey:. For example, if you have an NSArray property called items, you can get this property with valurForKey:@”items”. In the previous key search pattern for valueForKey:, we found that KVC actually uses a more flexible approach to managing container classes. Apple’s official documentation also recommends implementing these special accessors.

When the object attributes are mutable containers, for ordered containers, use the following method:

支那

 (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
Copy the code

This method returns a mutable ordered array, and if called, KVC searches in the following order

  • searchinsertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex:orinsert<Key>AdIndexes , remove<Key>AtIndexesMethod of formatting

    If we find at least oneinsertMethods and aremoveMethod, then also returns an available responseNSMutableArrayA collection of all method proxies (class name isNSKeyValueFastMutableArray2), then send to the agent collectionNSMutableArrayThe method toinsertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex:orinsert<Key>AdIndexes , remove<Key>AtIndexesCall in the form of a combination. There are also two interfaces that can be implemented optionally:replaceOnjectAtIndex:withObject:.replace<Key>AtIndexes:with<Key>:.
  • If the previous method is not found, searchset<Key>:Format method, if found, then sent to the agent collectionNSMutableArrayEventually they callset<Key>:Methods. In other words,mutableArrayValueForKey:Take out the agent collection after modification withset<Key>:Reassign and go back. It would be much less efficient. Therefore, it is recommended to implement the above method.
  • If the method of the previous step has not been found, check the class method again+ (BOOL)accessInstanceVariablesDirectlyIf YES(the default behavior) is returned, the_<key>.<key>, search for member variable names, if found, then sendNSMutableArrayMessage methods are handled directly by this member variable.
  • If still not found, callvalueForUndefinedKey:.
  • aboutmutableArrayValueForKey:I searched a lot on the Internet and found that it is generally used in the rightNSMutableArrayAdd an Observer. If the object property isNSMutableArray, NSMutableSet, NSMutableDictionaryWhen you add KVO to a collection type, you will find that you do not receive changes when you add or remove elements. Because the nature of KVO is that when the system detects a memory address or constant change for a property, it adds it- (void)willChangeValueForKey:(NSString *)keyand- (void)didChangeValueForKey:(NSString *)keyMethod to send notifications, so one solution is to manually call both methods, but it’s not recommended, you never really know as the system does when this element is changed. The other is to usemutableArrayValueForKey:.
@interface demo : NSObject @property (nonatomic,strong) NSMutableArray* arr; @end @implementation demo -(id)init{ if (self == [super init]){ _arr = [NSMutableArray new]; [self addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];  } return self; } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"%@",change); } -(void)dealloc{ [self removeObserver:self forKeyPath:@"arr"]; } -(void)addItem{[_arr addObject:@"1"]; } -(void)addItemObserver{ [[self mutableArrayValueForKey:@"arr"] addObject:@"1"]; } -(void)removeItemObserver{ [[self mutableArrayValueForKey:@"arr"] removeLastObject]; } @end then: demo* d = [demo new]; [d addItem]; [d addItemObserver]; [d removeItemObserver]; 2016-04-18 17:48:22.675 KVCDemo[32647:505864] {indexes = "<_NSCachedIndexSet: 0x100202C70 >[Number of indexes: 1 (in 1 ranges), indexes: (1)]"; kind = 2; new = ( 1 ); } 2016-04-18 17:48:22.677 KVCDemo[32647:505864] {indexes = "<_NSCachedIndexSet: 0x100202C70 >[Number of indexes: 1 (in 1 ranges), indexes: (1)]"; kind = 3; old = ( 1 ); }Copy the code

As you can see from the code above, the Observer does not call back when you simply call [_arr addObject:@”1″], just [[self mutableArrayValueForKey:@”arr”] addObject:@”1″]; This will trigger KVO correctly when writing. In the printed data, you can see the details of this operation. Kind may refer to the operation method (I am not sure yet). Old and new are not paired, new is used when adding new data, and old is used when deleting data

For unordered containers, use the following method

- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
Copy the code

This method returns a mutable unordered array. If called, KVC searches in the following order

  • searchaddObject<Key>Object: , remove<Key>Object:oradd<Key> , remove<Key>Method of formatting

    If we find at least oneinsertMethods and aremoveMethod, then also returns an available responseNSMutableSetA collection of all method proxies (class name isNSKeyValueFastMutableSet2), then send to the agent collectionNSMutableSetThe method toaddObject<Key>Object: , remove<Key>Object:oradd<Key> , remove<Key>Call in the form of a combination. There are also two interfaces that can be implemented optionally:intersect<Key> , set<Key>:
  • ifreceiverisManagedObjectThen the search will not continue.
  • If the previous method is not found, searchset<Key>: format method, if found, then sent to the agent collectionNSMutableSetEventually they callset<Key>:Methods. In other words,mutableSetValueForKeyTake out the agent collection after modification withset<Key>:Reassign and go back. It would be much less efficient. Therefore, it is recommended to implement the above method.
  • If the method of the previous step is not found, check the class method again+ (BOOL)accessInstanceVariablesDirectly, if returnYES(Default behavior), will press_<key>.<key>Search for member variable names in order, if found, then sentNSMutableSetMessage methods are handled directly by this member variable.
  • If still not found, callvalueForUndefinedKey:

    Visible, except for inspectionreceiverisManagedObjectOther than, its search order andmutableArrayValueForKeyBasic one,

Again, they have keyPath versions

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
Copy the code

There is also a mutableOrdered version for iOS5 and OSX10.7

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key
Copy the code

The usage of these two TYPES of KVC is not clear to me, so far I can only find examples for KVO. Please let me know if any readers can use it in their projects.

KVC and dictionary

When using KVC on NSDictionary objects, valueForKey: behaves the same as objectForKey:. So valueForKeyPath: is a convenient way to access multiple nested dictionaries.

There are two more methods for NSDictionary in KVC

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
Copy the code

DictionaryWithValuesForKeys: refers to input a set of key, returns the set of key corresponding to the attribute, then form a dictionary. SetValuesForKeysWithDictionary is used to modify the Model of the corresponding key attributes. Now it’s a little bit more intuitive to just use the code

Address* add = [Address new]; add.country = @"China"; add.province = @"Guang Dong"; add.city = @"Shen Zhen"; add.district = @"Nan Shan"; NSArray* arr = @[@"country",@"province",@"city",@"district"]; NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; NSLog(@"%@",dict); NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"}; [add setValuesForKeysWithDictionary:modifyDict]; / / the key Value to modify the Model attribute NSLog (@ "country: % @ province: % @ city: % @", the add. The country, the add. Province, add. City); // Print the result 2016-04-19 11:54:30.846 KVCDemo[6607:198900] {city = "Shen Zhen"; country = China; district = "Nan Shan"; province = "Guang Dong"; } 2016-04-19 11:54:30.847 KVCDemo[6607:198900] Country :USA Province: California City :Los AngleCopy the code

The printout was exactly as expected.

KVC’s internal implementation mechanism

Previously we examined how KVC searches for keys. So if you understand the search order of keys, you can write your own code to implement KVC. With sets and keyPath in mind, the implementation of KVC can be complicated, so we just write code to implement the most common values and set values.

@interface NSObject(MYKVC) -(void)setMyValue:(id)value forKey:(NSString*)key; -(id)myValueforKey:(NSString*)key; @end @implementation NSObject(MYKVC) -(void)setMyValue:(id)value forKey:(NSString *)key{ if (key == nil || key.length == 0) {// the key name must be valid return; } if ([value isKindOfClass:[NSNull class]]) { [self setNilValueForKey:key]; SetMyNilValueForKey = setMyNilValueForKey = setMyNilValueForKey; } if (! [value isKindOfClass:[NSObject class]]) { @throw @"must be s NSObject type"; return; } NSString* funcName = [NSString stringWithFormat:@"set%@:",key.capitalizedString]; If ([self respondsToSelector: NSSelectorFromString (funcName)]) {/ / the default priority call set method [the self performSelector:NSSelectorFromString(funcName) withObject:value]; return; } unsigned int count; BOOL flag = false; Ivar* vars = class_copyIvarList([self class], &count); for (NSInteger i = 0; i<count; i++) { Ivar var = vars[i]; NSString* keyName = [[NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding] substringFromIndex:1];  if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) { flag = true; object_setIvar(self, var, value); break; } if ([keyName isEqualToString:key]) { flag = true; object_setIvar(self, var, value); break; } } if (! flag) { [self setValue:value forUndefinedKey:key]; Self setMyValue:value forUndefinedKey:key if you want to customize it completely, you need to write self setMyValue:value forUndefinedKey:key, but it's not necessary, Is omitted}} - (id) myValueforKey: (nsstrings *) key {the if (key = = nil | | key. The length = = 0) {return [NSNull new]; NSString* funcName = [NSString stringWithFormat:@"gett%@:",key.capitalizedString];  if ([self respondsToSelector:NSSelectorFromString(funcName)]) { return [self performSelector:NSSelectorFromString(funcName)]; } unsigned int count; BOOL flag = false; Ivar* vars = class_copyIvarList([self class], &count); for (NSInteger i = 0; i<count; i++) { Ivar var = vars[i]; NSString* keyName = [[NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding] substringFromIndex:1];  if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) { flag = true; return object_getIvar(self, var); break; } if ([keyName isEqualToString:key]) { flag = true; return object_getIvar(self, var); break; } } if (! flag) { [self valueForUndefinedKey:key]; } return [NSNull new];} return [NSNull new];} return [NSNull new];} return [NSNull new]; } @end Address* add = [Address new]; add.country = @"China"; add.province = @"Guang Dong"; add.city = @"Shen Zhen"; add.district = @"Nan Shan"; [add setMyValue:nil forKey:@"area"]; // Test set nil value [add setMyValue:@"UK" forKey:@"country"]; [add setMyValue:@"South" forKey:@"area"]; [add setMyValue:@"300169" forKey:@"postCode"]; NSLog(@"country:%@ province:%@ city:%@ postCode:%@",add.country,add.province,add.city,add._postCode); NSString* postCode = [add myValueforKey:@"postCode"]; NSString* country = [add myValueforKey:@"country"]; NSLog(@"country:%@ postCode: %@",country,postCode); // Print the result: KVCDemo[7273:275129] Country :UK Province :South City :Shen Zhen postCode:300169 2016-04-19 14:29:39.499 KVCDemo[7273:275129] Country :UK postCode: 300169Copy the code

Above is to write their own code to achieve part of the function of KVC. I omitted the custom KVC error method and part of the KVC search key step, but the logic is very clear and the subsequent test is also in line with expectations. Of course, this is just my way of implementing KVC, and Apple probably doesn’t do it that way.

KVC correctness verification

KVC provides attribute values that are used to verify that the Value corresponding to a key is available

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
Copy the code

The default implementation of this method is to check whether the class has a method like this: -(BOOL)validate

:error: returns YES if the class has a method like this

@implementation Address -(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ // we're going to put this method in implementation, it's going to verify that we've set an invalid value NSString* country = *value; country = country.capitalizedString; if ([country isEqualToString:@"Japan"]) { return NO; // If the country is Japan, return NO,} return YES; } @end NSError* error; id value = @"japan"; NSString* key = @"country"; BOOL result = [add validateValue:&value forKey:key error:&error]; // if not overwritten -(BOOL)-validate<Key>:error:, default return Yes if (result) {NSLog(@" Key "); [add setValue:value forKey:key]; } else{NSLog(@" key mismatch "); } NSString* country = [add valueForKey:@"country"]; NSLog(@"country:%@",country); 2016-04-20 14:55:12.055 KVCDemo[867:58871] The key value does not match 2016-04-20 14:55:12.056 KVCDemo[867:58871] country:ChinaCopy the code

When developers need to verify that they can set a value with KVC, they can call validateValue as shown above: If the class developer implements -(BOOL)validate

:error: KVC calls this method directly and returns YES if it does not. It needs to be verified manually by the developer. So even if you write validation methods in the class, KVC will still be able to set the value because it will not actively validate.

The use of KVC

KVC is an indispensable tool in iOS development. This runtime based programming method greatly improves flexibility, simplifies code, and even achieves many unimaginable functions. KVC is also the foundation of many iOS development dark arts. Here’s a list of KVC usage scenarios for iOS development

Value and set values dynamically

The use of KVC dynamic values and set values is the most basic use. I believe every iOS developer can master it,

Use KVC to access and modify private variables

Private attributes in classes are not directly accessible in Objective-C, but KVC is, as shown in the Dog class example earlier in this article.

Model and dictionary conversion

Fully utilizing the KVC and Objc Runtime combination techniques, a lot of functionality is accomplished in just a few lines of code

Modify the internal properties of some controls

This is an essential trick in iOS development. It is well known that many UI controls are composed of many internal UI controls, but Apple degree does not provide the API to access these controls, so we can not access and modify the style of these controls. KVC can solve this problem in most cases. Metagtext is the most common placeHolderText in the personalized UITextField. Here’s how to modify the placeHolder text style. The key point here is that if you get the property name of the style you want to modify, that is, the key or keyPath name

In general, you can use runtime to get property names that Apple doesn’t want open

let count:UnsafeMutablePointer<UInt32> = UnsafeMutablePointer<UInt32>() var properties = class_copyIvarList(UITextField.self, count) while properties.memory.debugDescription ! = "0x0000000000000000"{ let t = ivar_getName(properties.memory) let n = NSString(CString: t, encoding: NSUTF8StringEncoding) print(n) Succeeded () properties = properties.succeeded ()} // Some attributes Optional(_disabledBackgroundView) Optional(_systemBackgroundView) Optional(_floatingContentView) Optional(_contentBackdropView) Optional(_fieldEditorBackgroundView) Optional(_fieldEditorEffectView) Optional(_displayLabel) Optional(_placeholderLabel) // This is the attribute I want to change. Optional(_dictationLabel) Optional(_suffixLabel) Optional(_prefixLabel) Optional(_iconViewCopy the code

You can see that there are many other things that can be modified, using the KVC setting can get the effect you want.

Operating collection

Apple makes special implementations of KVC’s valueForKey: methods. Container classes like NSArray and NSSet implement these methods. So you can manipulate collections very easily with KVC

High order messaging with KVC

When using KVC on a container class, valueForKey: is passed to every object in the container, not the container itself. The result is added to the returned container so that the developer can easily manipulate the collection to return another collection.

NSArray* arrStr = @[@"english",@"franch",@"chinese"]; NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"]; for (NSString* str in arrCapStr) { NSLog(@"%@",str); } NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"]; for (NSNumber* length in arrCapStrLength) { NSLog(@"%ld",(long)length.integerValue); } 2016-04-20 16:29:14.239 KVCDemo[1356:118667] English 2016-04-20 16:29:14.240 KVCDemo[1356:118667] Franch 2016-04-20 16:29:14.240 KVCDemo[1356:118667] Chinese 2016-04-20 16:29:14.240 KVCDemo[1356:118667] 7 2016-04-20 16:29:14.241 KVCDemo[1356:118667] 6 2016-04-20 16:29:14.241 KVCDemo[1356:118667] 7Copy the code

The method capitalizedString is passed to each item in the NSArray, so that each member of the NSArray executes capitalizedString and returns a new NSArray with the result. As you can see from the print, all strings were successfully converted to uppercase. The valueForKeyPath: method can also be used if you want to perform multiple methods. It calls capitalizedString for each member first, then Length, and since lenth returns a number, the result is stored in the new array as an NSNumber.

Manipulate collections with functions in KVC

Simple set operator: @avg, @count, @max, @min, @sum5 simple set operator: @avg, @count, @max, @min, @sum5 simple set operator:

@interface Book : NSObject @property (nonatomic,copy) NSString* name; @property (nonatomic,assign) CGFloat price; @end @implementation Book @end Book *book1 = [Book new]; book1.name = @"The Great Gastby"; book1.price = 22; Book *book2 = [Book new]; book2.name = @"Time History"; book2.price = 12; Book *book3 = [Book new]; book3.name = @"Wrong Hole"; book3.price = 111; Book *book4 = [Book new]; book4.name = @"Wrong Hole"; book4.price = 111; NSArray* arrBooks = @[book1,book2,book3,book4]; NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"]; NSLog(@"sum:%f",sum.floatValue); NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"]; NSLog(@"avg:%f",avg.floatValue); NSNumber* count = [arrBooks valueForKeyPath:@"@count"]; NSLog(@"count:%f",count.floatValue); NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"]; NSLog(@"min:%f",min.floatValue); NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"]; NSLog(@"max:%f",max.floatValue); 2016-04-20 16:45:54.696 KVCDemo[1484:127089] sum:256.000000 2016-04-20 16:45:54.697 KVCDemo[1484:127089] Avg :64.000000 2016-04-20 16:45:54.697 KVCDemo[1484:127089] count:4.000000 2016-04-20 16:45:54.697 KVCDemo[1484:127089] Min :12.000000 2016-04-20 16:45:54.697 KVCDemo[1484:127089] Max :111.000000Copy the code

② The object operator is slightly more complex than the set operator, and can return the specified content as an array. There are two kinds of operators: @distinctUnionofObjects @unionofObjects they all return NSArray. The difference is that the distinctUnionOfObjects will return a unique element that is the result of the deduplication. The latter returns a complete set of elements. Usage:

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); } 2016-04-20 16:47:34.490 KVCDemo[1522:128840] distinctUnionOfObjects 2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000 2016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.000000 2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.000000 2016-04-20 16:47:34.490 KVCDemo[1522:128840] unionOfObjects 2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.000000 2016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.000000 2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000 The 2016-04-20 16:47:34. 490 KVCDemo [1522-128840] 111.000000Copy the code

The former will remove the duplicate prices and return all prices, the latter directly return all book prices. (Because only the price is returned, and no books are returned, it feels useless.) The Array and Set operators include sets within sets, and we execute the following code: @distinctUnionofArrays @unionofArrays @distinctUnionofSets @distinctUnionofArrays: This operation returns an array of different objects in the @unionofArrays specified property from the critical path to the right of the operator and this operation returns an array, This array contains objects that are different from @distinctunionofArrays in the properties specified from the critical path to the right of the operator. Duplicate objects will not be removed. @distinctUnionofSets is similar to @distinctunionofArrays. Because Set itself does not support repetition.

KVO

You read that right, KVO is based on KVC. So how to implement KVO with KVC, please look forward to the next chapter.

conclusion

This paper introduces the principle and usage of KVC in all directions. I believe that readers will have a more complete understanding of KVC after reading it, and will make better use of KVC in the project. In fact, all of these things are explained in detail in the official documentation. It’s just that it’s all in English, and I’ve seen it a few times, but it’s hard for me to read if I don’t have good English. For example, I thought about the sentence in the official introduction of @distinctUnionofArrays for a long time and didn’t quite understand it. Besides, the official sample code isn’t good enough, so it’s hard to figure out how to apply certain features. But I do recommend that developers learn English well and read the official documentation. Combine StackOverFlow with Google. It really solves most of your development challenges. That’s all for this article, and finally for a wave of praise and attention. For more information, please join the ** circle ** to get it

####### original address