To prepare

  • Key-value Coding Programming Guide

I. Introduction to KVC

What is the KVC

  • KVCIs the full name ofKey-Value Coding(key-value code), is made up ofNSKeyValueCodingA mechanism enabled by informal protocol by which an object provides indirect access to its properties. Member variables of an object or their associated access methods can be accessed through strings (getter or setter).
  • Often, we can access an object’s properties directly through an access method or variable name. We can also useKVCAccesses the properties of an object indirectly, andKVCYou can also access private variables. In some cases,KVCIt also helps simplify the code.

Accessing object properties

1. The common API

- (nullable id)valueForKey:(NSString *)key; - (nullable id)valueForKeyPath:(NSString *)keyPath; - (void)setValue (nullable id)value forKey:(NSString *)key; - (void)setValue (nullable ID)value forKeyPath (NSString *)keyPath; // Assign by keyPathCopy the code

2. Perform basic operations

SSLPerson, SSLPackage:

@interface SSLPerson : NSObject
@property (nonatomic, copy)   NSString   *name;
@property (nonatomic, strong) SSLPackage *package;
@end

@interface SSLPackage : NSObject
@property (nonatomic, copy) NSNumber *money;
@end
Copy the code

You can assign a value to the name property using the setter method:

person.name = @"SSL";
Copy the code

You can also assign a value to the name attribute via KVC:

[person setValue:@"SSL" forKey:@"name"];
Copy the code

3. KeyPath

KVC also supports multi-level access, using KeyPath in the same way as dot syntax. For example, we want to assign a value to the money property of person’s package property with its KeyPath set to package.money.

[person setValue:@1000000 forKeyPath:@"package.money"];
Copy the code

4. Multiple value operations

Given a set of keys, get a set of values, which are returned as a dictionary. This method calls the valueForKey: method for each Key in the array.

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

Sets the value in the specified dictionary to the message receiver’s property, identifying the property with the Key of the dictionary. The default implementation is to call the setValue:forKey: method for each key-value pair, replacing the NSNull object with nil as needed.

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

Second, Apple official documents explain KVC

KVC:

About Key-Value Coding

Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key- value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.

Typically use accessor methods to gain access to an object’s properties. A get accessor (or getter) returns the value of a property. A set accessor (or setter) sets the value of a property. In Objective-C, You can also obtain direct access to a property’s underlying instance variable ways is straightforward, but requires calling on a property-specific method or variable name. As the list of properties grows or changes, so also must the code which accesses these properties. In contrast, a key-value coding compliant object provides a simple messaging interface that is consistent across all of its properties.

Key-value coding is a fundamental concept that underlies many other Cocoa technologies, such as key-value observing, Cocoa bindings, Core Data, and AppleScript-ability. Key-value coding can also help to simplify your code in some cases.

Three, KVC set value and value process

KVC setting process

Open apple’s official document to check the KVC setting principle:

Search Pattern for the Basic Setter

The default implementation of setValue:forKey:, given key and valueparameters as input, attempts to set a property named key to value (or, for non-object properties, the unwrapped version of  value, as described in Representing Non-Object Values) inside the object receiving the call, using the following procedure:

  1. Look for the first accessor named set<Key>: or _set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.
  2. If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like _<key>._is<Key>.<key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.
  3. Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.
  • If you look at the documentation,KVCThere are three steps in the setting process of, which are analyzed below.

1. set<Key>: / _set<Key>

Search methods in the order set

:, _set

:. If found, call it and pass the value in, otherwise go to the next step, and then verify with the example.

Create the SSLPerson class and implement the setName:, _setName: methods:

@interface SSLPerson : NSObject
@end
@implementation SSLPerson
- (void)setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name {
    NSLog(@"%s - %@",__func__,name);
}
@end
Copy the code

KVC set value code call:

SSLPerson *person = [[SSLPerson alloc] init];
[person setValue:@"SSL" forKey:@"name"];
Copy the code

View the results:

-[SSLPerson setName:] - SSL
Copy the code
  • setName:Was successfully called,_setName:Is not called, indicating that these two methods are not called repeatedly.
  • Note: If declarednameProperty, even thoughsetName:No implementation,_setNameIt’s not going to be called.

Remove setName: method:

@interface SSLPerson : NSObject
@end
@implementation SSLPerson
- (void)_setName:(NSString *)name {
    NSLog(@"%s - %@",__func__,name);
}
@end
Copy the code

Running the program again, you can see that the _setName: method is called:

-[SSLPerson _setName:] - SSL
Copy the code

Note: Set

: can also be called when testing.

2. _<key>._is<Key>.<key>, or is<Key>

Check the message receiver class + accessInstanceVariablesDirectly method return values (default returns YES). If YES is returned, the member variables are found in the order _

, _is< key>,

, and is< key>. If found, assign value to it, otherwise go to the next step. If + accessInstanceVariablesDirectly method returns NO also perform the next step.

Create test code:

@interface SSLPerson : NSObject{
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
@end
@implementation SSLPerson
@end

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

To view the print:

_name = SSL
_isName = (null)
name = (null)
isName = (null)
Copy the code
  • only_nameIs assigned, no problem.

Test code to remove _name:

@interface SSLPerson : NSObject{
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
@end
@implementation SSLPerson
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    SSLPerson *person = [[SSLPerson alloc] init];
    [person setValue:@"SSL" forKey:@"name"];
    
    NSLog(@"_isName = %@",person->_isName);
    NSLog(@"name = %@",person->name);
    NSLog(@"isName = %@",person->isName);
}
Copy the code

To view the print:

_isName = SSL
name = (null)
isName = (null)
Copy the code
  • only_isNameIt’s assigned, no problem, and the other two cases are tested, too.

3. setValue:forUndefinedKey:

Call setValue: forUndefinedKey: method, the method throws an exception NSUnknownKeyException, and cause the program to Crash. This is the default implementation, so we can override this method and do some special processing based on a particular key.

KVC value process

Open the official apple document to check the KVC value principle:

Search Pattern for the Basic Getter

The default implementation of valueForKey:, given a key parameter as input, carries out the following procedure, operating from within the class instance receiving the valueForKey: call.

  1. Search the instance for the first accessor method found with a name like get<Key>.<key>.is<Key>, or _<key>, in that order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step.
  2. If no simple accessor method is found, search the instance for methods whose names match the patterns countOf<Key> and objectIn<Key>AtIndex:(corresponding to the primitive methods defined by the NSArray class) and <key>AtIndexes: (corresponding to the NSArray method objectsAtIndexes:).

    If the first of these and at least one of the other two is found, create a collection proxy object that responds to all NSArray methods and return that. Otherwise, proceed to step 3.

    The proxy object subsequently converts any NSArray messages it receives to some combination of countOf<Key>.objectIn<Key>AtIndex:, and <key>AtIndexes: messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name like get<Key>:range:, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were anNSArray, even if it is not.
  3. If no simple accessor method or group of array access methods is found, look for a triple of methods named countOf<Key>.enumeratorOf<Key>, and memberOf<Key>: (corresponding to the primitive methods defined by the NSSet class).

    If all three methods are found, create a collection proxy object that responds to all NSSet methods and return that. Otherwise, proceed to step 4.

    This proxy object subsequently converts any NSSet message it receives into some combination of countOf<Key>.enumeratorOf<Key>, and memberOf<Key>:messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were anNSSet, even if it is not.
  4. If no simple accessor method or group of collection access methods is found, and if the receiver’s class method accessInstanceVariablesDirectlyreturns YES, search for an instance variable named _<key>._is<Key>.<key>, or is<Key>, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6.
  5. If the retrieved property value is an object pointer, simply return the result.

    If the value is a scalar type supported by NSNumber, store it in an NSNumberinstance and return that.

    If the result is a scalar type not supported by NSNumber, convert to an NSValueobject and return that.
  6. If all else fails, invoke valueForUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.]

1. get<Key>.<key>.is<Key>, or _<key>

Find methods in the order of GET

,

, is

, and _< Key>. If found, call the value and perform 5, otherwise go to the next step;

2. countOf<Key>,objectIn<Key>AtIndex:,<key>AtIndexes:

Look for countOf

, objectIn

AtIndex:, and

AtIndexes: naming methods. If the first and at least one of the next two are found, a collection proxy object (of type NSKeyValueArray, inherited from NSArray) that responds to all methods of NSArray is created and returned. Otherwise, go to 3.

  • The proxy object will then receive anyNSArrayMessage conversion tocountOf<Key>,objectIn<Key>AtIndex:,<Key>AtIndexes:Message combination and send it toKVCThe caller. If the original object also implements a name calledget<Key>:range:The proxy object will also use this method when appropriate.
  • whenKVCCallers working with proxy objects allow the underlying properties to behave likeNSArraySame, even if it’s notNSArray.

3. countOf<Key>,enumeratorOf<Key>,memberOf<Key>:

Find countOf

, enumeratorOf

, memberOf

: named methods. If all three methods are found, a collection proxy object (of type NSKeyValueSet, inherited from NSSet) that responds to all methods of the NSSet is created and returned. Otherwise, go to 4.

  • The proxy object will then receive anyNSSetMessage conversion tocountOf<Key>,enumeratorOf<Key>,memberOf<Key>:Message combination and send it toKVCThe caller.
  • whenKVCCallers working with proxy objects allow the underlying properties to behave likeNSSetSame, even if it’s notNSSet.

4. +accessInstanceVariablesDirectly

Check the message receiver class + accessInstanceVariablesDirectly method return values (default returns YES). If YES is returned, the member variables are found in the order _

, _is< key>,

, and is< key>. If the value is found, perform 5; otherwise, perform 6. If + accessInstanceVariablesDirectly method returns NO also perform 6.

5. Convert data types as required

If the fetched value is an object pointer, that is, the object is retrieved, then the object is returned directly. If the fetched value is of a data type supported by NSNumber, it is stored in the NSNumber instance and returned. If the fetched value is not of a data type supported by an NSNumber, it is converted to an NSValue object and returned.

6. valueForUndefinedKey:

Call the valueForUndefinedKey: method, which throws an exception NSUnknownKeyException and causes the program to Crash. This is the default implementation, so we can override this method and do some special processing based on a particular key.

Four, KVC custom implementation

Custom Settings

- (void)ssl_setValue:(nullable id)value forKey:(NSString *)key { // 1: The key to empty the if (key = = nil | | key. The length = = 0) {return; } // 2: set<Key>: or _set<Key>, NSString *Key = key.capitalizedString; // 2: set<Key>: NSString *Key = key.capitalizedString; NSString *setKey = [NSString stringWithFormat:@"set%@:",Key]; NSString *setKey = [NSString stringWithFormat:@"set%@:",Key]; NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key]; NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key]; if ([self ssl_performSelectorWithMethodName:setKey value:value]) { NSLog(@"*********%@**********",setKey); return; }else if ([self ssl_performSelectorWithMethodName:_setKey value:value]) { NSLog(@"*********%@**********",_setKey); return; }else if ([self ssl_performSelectorWithMethodName:setIsKey value:value]) { NSLog(@"*********%@**********",setIsKey); return; } / / 3: determine whether response accessInstanceVariablesDirectly returns YES NO melt / / 3: determine whether can direct assignment instance variables if (! [self.class accessInstanceVariablesDirectly] ) { @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil]; NSMutableArray *mArray = [self getIvarListName]; NSMutableArray *mArray = [self getIvarListName]; // _<key> _is<Key> <key> is<Key> NSString *_key = [NSString stringWithFormat:@"_%@",key]; NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key]; NSString *isKey = [NSString stringWithFormat:@"is%@",Key]; If ([mArray containsObject:_key]) {// get ivar ivar ivar = class_getInstanceVariable(self class), _key.UTF8String); // 4.3 Setting the ivar value object_setIvar(self, ivar, value); return; }else if ([mArray containsObject:_isKey]) { Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String); object_setIvar(self , ivar, value); return; }else if ([mArray containsObject:key]) { Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); object_setIvar(self , ivar, value); return; }else if ([mArray containsObject:isKey]) { Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String); object_setIvar(self , ivar, value); return; } @throw [NSException exceptionWithName:@"LGUnknownKeyException" Reason :[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil]; }Copy the code

Custom value

- (nullable id) ssl_valueForKey: (nsstrings *) key {/ / 1: choose brush key judgment is not empty if (key = = nil | | key. The length = = 0) {return nil. Get <Key> <Key> countOf<Key> objectIn<Key>AtIndex // Key to uppercase NSString *Key = key.capitalizedString; NSString *getKey = [NSString stringWithFormat:@"get%@",Key]; NSString *getKey = [NSString stringWithFormat:@"get%@",Key]; NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key]; NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" if ([self respondsToSelector:NSSelectorFromString(getKey)]) { return [self performSelector:NSSelectorFromString(getKey)]; }else if ([self respondsToSelector:NSSelectorFromString(key)]){ return [self performSelector:NSSelectorFromString(key)];  }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){ if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) { int num = (int)[self performSelector:NSSelectorFromString(countOfKey)]; NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1]; for (int i = 0; i<num-1; i++) { num = (int)[self performSelector:NSSelectorFromString(countOfKey)]; } for (int j = 0; j<num; j++) { id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)]; [mArray addObject:objc]; } return mArray; }} #pragma clang diagnostic pop // 3: determine whether to directly assign instance variable if (! [self.class accessInstanceVariablesDirectly] ) { @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil]; NSMutableArray *mArray = [self getIvarListName]; // _<key> _is<Key> <key> is<Key> // _name -> _isName -> name -> isName NSString *_key = [NSString stringWithFormat:@"_%@",key]; NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key]; NSString *isKey = [NSString stringWithFormat:@"is%@",Key]; if ([mArray containsObject:_key]) { Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String); return object_getIvar(self, ivar);; }else if ([mArray containsObject:_isKey]) { Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String); return object_getIvar(self, ivar);; }else if ([mArray containsObject:key]) { Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); return object_getIvar(self, ivar);; }else if ([mArray containsObject:isKey]) { Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String); return object_getIvar(self, ivar);; } return @""; }Copy the code

Relevant methods

# pragma mark - relevant methods - (BOOL) ssl_performSelectorWithMethodName: (nsstrings *) methodName value {value: (id) if ([the self respondsToSelector:NSSelectorFromString(methodName)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self performSelector:NSSelectorFromString(methodName) withObject:value]; #pragma clang diagnostic pop return YES; } return NO; } - (id)performSelectorWithMethodName:(NSString *)methodName{ if ([self respondsToSelector:NSSelectorFromString(methodName)]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" return [self performSelector:NSSelectorFromString(methodName) ]; #pragma clang diagnostic pop } return nil; } - (NSMutableArray *)getIvarListName{ NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1]; unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i<count; i++) { Ivar ivar = ivars[i]; const char *ivarNameChar = ivar_getName(ivar); NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar]; NSLog(@"ivarName == %@",ivarName); [mArray addObject:ivarName]; } free(ivars); return mArray; }Copy the code

expand

  • DIS_KVC_KVO