preface

“This is the 12th day of my participation in the August Text Challenge.

Resources to prepare

  • Key-value Coding Programming Guide

The summary of KCV

define

  • KVCIs the full name ofKey-Value Coding(key value encoding), is made byNSKeyValueCodingInformal protocol-enabled mechanism by which objects provide indirect access to their properties, complementing the direct access provided by instance variables and their associated access methods, through strings to access an object’s member variables or their associated access methods. When an object conforms to a key-value encoding, its properties can be addressed by string arguments through a compact, uniform messaging interface.

In Objective-C, KVC is the equivalent of NSObject classification. Look at the setValueForKey method, which is in Foundation, and Foundation framework is not open source, you can only find it in the official apple documentation. Let’s look at the API, which is the NSKeyValueCoding file for the Foundation framework:

Essentially, NSKeyValueCoding classification for NSObject, NSArray, NSDictionary, NSMutableDictionary, NSOrderedSet, NSSet, etc. Let them have key-value Coding capability.

APIintroduce

  • throughKeyRead and store:
- (nullable id)valueForKey:(NSString *)key; 
- (void)setValue:(nullable id)value forKey:(NSString *)key;
Copy the code
  • throughkeyPathRead and store:
- (nullable id)valueForKeyPath:(NSString *)keyPath; 
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
Copy the code
  • otherAPI:
/ / the default returns YES, said if not found the Set < Key > method, according to _key, _iskey, Key, iskey sequential search members + (BOOL) accessInstanceVariablesDirectly; //KVC provides an API for verifying the correctness of attribute values, which can be used to check whether the value of set is correct. - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError; // This is the set operation API, there are a series of apis, if the property is an NSMutableArray, So you can use this method to return - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; // if the Key does not exist and KVC cannot find any fields or attributes related to the Key, this method is called. The default is to throw an exception - (nullable id)valueForUndefinedKey:(NSString *) Key; - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; // if you pass nil to Value in the SetValue method, this method is called - (void)setNilValueForKey:(NSString *)key; // Enter a set of keys, return the values corresponding to the keys, and return the dictionary, Used to transfer the Model to a dictionary - (NSDictionary < nsstrings *, id > *) dictionaryWithValuesForKeys: (NSArray keys < > nsstrings * *);Copy the code

APIuse

Object properties

  • Properties: These are simple values, such as scalar, string, or Boolean values. The value objectNSNumberAnd other immutable typesNSColorAlso known as attributes;
  • One-to-one relationships: These are mutable objects with their own properties. Properties of an object can be changed without changing the object itself;
  • Pto-many relationships: These are set objects. Usually useNSArrayOr an instance of theNSSetTo hold such a collection, possibly of a custom collection type;

throughKeyThe query

LGPerson *person = [[LGPerson alloc] init]; // Assign the 'name' attribute via 'setter' methods: person.name = @"LG_Cooci"; person.age = 20; person->myName = @"cooci"; // 1: key-value Coding (KVC) : basic type - look at the underlying principle // informal protocol - indirect access [person setValue:@"KC" forKey:@"name"];Copy the code

throughkeyPathThe query

LGStudent *student = [LGStudent alloc]; Student. Subject = @" harvest "; person.student = student; [person setValue:@"Swift" forKeyPath:@"student.subject"]; NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);Copy the code

Obtained print result: Swift; Substitution is made by KVC combined with the path student.subject.

A collection of attributes

External API interface:

  • rightNSMutableArray:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; / / by key - (NSMutableArray *) mutableArrayValueForKeyPath (keyPath nsstrings *); / / by keyPathCopy the code
  • rightNSMutableSet
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key; / / by key - (NSMutableSet *) mutableSetValueForKeyPath (keyPath nsstrings *); / / by keyPathCopy the code
  • rightNSMutableOrderedSet:
- (NSMutableOrderedSet *) mutableOrderedSetValueForKey: (nsstrings *) key API_AVAILABLE (macos (10.7), the ios (5.0), Tvos watchos (2.0), (9.0)); / / by key - (NSMutableOrderedSet *) mutableOrderedSetValueForKeyPath: (nsstrings *) keyPath API_AVAILABLE (macos (10.7), Ios (5.0), watchos (2.0), tvos (9.0)); / / by keyPathCopy the code

Can be implemented through the specific code to see:

  • An array of values
LGStudent *p = [LGStudent new]; p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil]; NSArray *arr = [p valueForKey:@"penArr"]; NSLog(@"pens = %@", arr); // Print arR array NSEnumerator *enumerator = [arr objectEnumerator]; NSString* str = nil; while (str = [enumerator nextObject]) { NSLog(@"%@", str); // go through print}Copy the code

Print arR array result:

Traversing the printed result:

  • Modifying an array element
person.array = @[@"1",@"2",@"3"]; NSArray *array = [person valueForKey:@"array"]; array = @[@"10",@"8",@"9"]; [person setValue:array forKey:@"array"]; NSLog(@"%@",[person valueForKey:@"array"]); // Use mutableArrayValueForKey: NSMutableArray *mArray = [Person mutableArrayValueForKey:@"array"]; mArray[0] = @"100"; NSLog(@"%@",[person valueForKey:@"array"]); // Print after modificationCopy the code

Print before modification:

Print after modification:

  • Model dictionary transformation
NSDictionary* dict = @{ @"name":@"Cooci", @"nick":@"KC", @"subject":@"iOS", @"age":@18, @"length":@190 }; LGStudent *s = [[LGStudent alloc] init]; / / dictionary model [s setValuesForKeysWithDictionary: dict]; NSLog(@" model: %@",s); NSArray *array = @[@"name",@"age"]; NSDictionary *dic = [s dictionaryWithValuesForKeys:array]; NSLog(@" dictionary: %@",dic);Copy the code

Dictionary to model transformation:

Key array to model to dictionary print:

  • KVCThe messaging
NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC"]; NSArray *lenStr= [array valueForKeyPath:@"length"]; // The message is passed from array to string NSLog(@"%@",lenStr); NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"]; // Keyword lowercaseString NSLog(@"%@",lowStr);Copy the code

Print string length:

Print string:

  • Set operator

There are three basic types of collection operators:

  • Aggregate operator: Somehow merges the objects of a collection and returns an object that normally matches the data type of the property named in the correct key path. The @count operator is an exception. It does not accept the correct key path and always returns an NSNumber instance;

  • Array operator: Returns an instance of NSArray that contains subsets of objects held in the named collection;

  • 3. Nested operators: Process collections containing other collections and, depending on the operator, return an instance of NSArray or NSSet to combine the objects of nested collections in some way;

Next, use the case to demonstrate.

  • Aggregate operator

Print the following results: array length, average value, array number, total value, maximum value, minimum value:

  • Array operator

Print result:

  • Nested operators

Print result:

Row weight :(row weight ---- arr = 185, 183, 179, 177) no row weight :(no row weight ---- arr1 = 177, 177, 177, 183, 185, 177, 183, 179, 179, 179, 179)Copy the code

Access non-object properties

  • The default key encoding implementation is usedNSNumberThe scalar type of the instance wrapper:

  • Default access is used to wrap and expand commonNSPoint,NSRange,NSRect, andNSSizeStructure:

  • Structure type that can be packaged in oneNSValueObject:

Structure:

Call:

Print result:

Validation of attributes

Attribute specific validation is done as follows:

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

There are three possible outcomes:

  • 1. Return YES if the attribute is valid and does not change value or no error is reported.

  • 2. If the value is invalid, choose not to change it. In this case, the method returns NO. And returns the error outError;

  • 3. If the value is invalid, a new valid object is created as a replacement. In this case, the method returns YES. Leave the error object unchanged. Before returning, the method modifies the value reference to point to the new value object. When it modifies, the method always creates a new object rather than modifying the old object, even if the value object is mutable;

KVCStored procedure of

By calling setValue:forKey:

  • Set

    → _set

    →setIs

    accessors search in order, if found, call it;


    • keyThe value is the name of a member variableKVCThe naming convention of the.
    • If there are any of themsetterMethod, you can set properties directlyvalueCome in;
  • If not find these setter methods, enter 2 steps: KVC mechanism will check + (BOOL) have accessInstanceVariablesDirectly method returns YES:

    • returnYES, search for indirectly accessed instance variables to assign, search order:_<key>_is<Key><key>is<Key>If any of the instance variables is found, it can be assigned a value
    • If none is found, returnNO, then steps will be entered3.;
  • (3) if a setter method or instance variables are not found, the system will invoke the object’s setValue: forUndefinedKey: method, the default thrown NSUndefinedKeyException type of exception;

Through case study:

#import <Foundation/Foundation.h> @interface LGPerson : NSObject{ @public NSString *_name; NSString *_isName; NSString *name; NSString *isName; } @ end @ implementation LGPerson # pragma mark - closed/open instance variable + (BOOL) accessInstanceVariablesDirectly {return YES; } @endCopy the code

A setter method

- (void)setName:(NSString *)name { NSLog(@"%s - %@",__func__,name); } - (void)_setName:(NSString *)name { NSLog(@"%s - %@",__func__,name); } - (void)setIsName:(NSString *)name { NSLog(@"%s - %@",__func__,name); } / / not call - (void) _setIsName: (nsstrings *) name {NSLog (@ - % @ "% s", __func__, name); }Copy the code

Schematic diagram:

KVCRead process of

The value is obtained by calling valueForKey:. The process is as follows:

  • Get

    –>

    –> is

    –> _< Key>. If found, different results are returned depending on the type of attribute value found:


    • If it is an object, the result is returned directly;
    • If it isBOOLorIntThe equivalent type will be wrapped as aNSNumberObject stored in theNSNumberInstance and return it;
  • If not, KVC will find countOf

    , objectIn

    AtIndex:, or

    AtIndexes:. Creates a collection proxy object that responds to all NSArray methods and returns this object, NSKeyValueArray, which is a subclass of NSArray. Any NSArray messages received by the proxy object will then be converted to some combination of countOf

    , objectIn

    AtIndex:, and

    AtIndexes: messages to create key-encoded objects.





There is, of course, an optional get

:range: method. 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.

  • 3., if still not found, then the following methods are searched simultaneously.countOf<Key>,enumeratorOf<Key>,memberOf<Key>:.

If all three methods are found, a collection proxy is returned that can respond to all NSSet methods. This proxy object then calls all NSSet messages it receives as countOf

, enumeratorOf

, memberOf

: some combination of messages.


  • (4), if not found, would then find accessInstanceVariablesDirectly method return values, if returns YES, search in turn _ < key >, _is < key >, < the key > or is < key > instance variable (not recommended, Because such direct access to the instance variable breaks encapsulation and makes the code more vulnerable), find the instance variable and fetch its value directly;

  • If NO is returned, the valueForUndefinedKey: method of the object is called. By default, an exception of type NSUndefinedKeyException is thrown.

Take a look at the following cases:

Look for the getter method first

//MARK: -valueForKey Process analysis -get <Key>, <Key>, is<Key>, or _< Key> - (NSString *)getName{return NSStringFromSelector(_cmd); } - (NSString *)name { return NSStringFromSelector(_cmd); } - (NSString *)isName { return NSStringFromSelector(_cmd); } - (NSString *)_name { return NSStringFromSelector(_cmd); }Copy the code

Look for the collection type

//MARK: - count of set types - (NSUInteger)countOfPens {NSLog(@"%s",__func__); return [self.arr count]; } // get value - (id)objectInPensAtIndex:(NSUInteger)index {NSLog(@"%s",__func__); return [NSString stringWithFormat:@"pens %lu", index]; }Copy the code

And then we look for the NSSet type

/ / MARK: - set / / the number - (NSUInteger) countOfBooks {NSLog (@ "% s", __func__); return [self.set count]; } / / object contains the member - (id) memberOfBooks: (id) object {NSLog (@ "% s", __func__); return [self.set containsObject:object] ? object : nil; } // iterator - (id)enumeratorOfBooks {// objectEnumerator NSLog(@" iterated "); return [self.arr reverseObjectEnumerator]; }Copy the code

Finding instance variables

#import <Foundation/Foundation.h> @interface LGPerson : NSObject { @public NSString *_name; NSString *_isName; NSString *name; NSString *isName; } @ end @ implementation LGPerson # pragma mark - closed/open instance variable + (BOOL) accessInstanceVariablesDirectly {return YES; } @endCopy the code

Illustration:

The customKVC

KVCstorage

  • 1. Judge whatkey;
  • 2,setter set<Key>: or _set<Key>;
  • 3. Determine whether to respondaccessInstanceVariablesDirectlyReturn the responseYES, no response is returnedNO, directly to collapse; Determine whether instance variables can be assigned directly;
  • 4. Indirect variables, acquisitionivar– > traversalcontainsObjct
    • 4.1 Define a mutable array to collect instance variables;
    • 4.2 Obtaining the correspondingivar;
    • 4.3 To the correspondingivarSet the value;
  • 5. If no relevant instance can be found;
- (void)lg_setValue:(nullable ID)value forKey:(NSString *)key{// KVC custom // 1: Judge what key if (key = = nil | | key. The length = = 0) {return; } // 2: setSet <Key>: or _set<Key>, // NSString *Key = key.capitalizedString; NSString *setKey = [NSString stringWithFormat:@"set%@:",Key]; NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key]; NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key]; if ([self lg_performSelectorWithMethodName:setKey value:value]) { NSLog(@"*********%@**********",setKey); return; }else if ([self lg_performSelectorWithMethodName:_setKey value:value]) { NSLog(@"*********%@**********",_setKey); return; }else if ([self lg_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]; } // 4: ivar -> pass through containsObjct - // 4.1 Define a mutable array to collect instance variables 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]) {// 4.2 Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String); Object_setIvar (self, ivar, value); // set 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

KVCThe values

  • 1, screeningkeyJudgment non-empty;
  • 2. Find ways to do itget<Key> <key> countOf<Key>  objectIn<Key>AtIndex;
  • 3. Determine whether instance variables can be assigned directly;
  • 4. Find relevant instance variables for assignment;
    • 4.1. Define a mutable array to collect instance variables
/ / 1: choose brush key judgment is not empty if (key = = nil | | key. The length = = 0) {return nil. } // 2: get<Key> <Key> countOf<Key> objectIn<Key>AtIndex 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: pragma clang diagnostic pop // 3: pragma Clang diagnostic pop // 3: pragma Clang diagnostic pop // [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) lg_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