Welcome to the iOS Exploration series.

  • IOS explores the alloc process
  • IOS explores memory alignment &malloc source code
  • IOS explores ISA initialization & pointing analysis
  • IOS exploration class structure analysis
  • IOS explores cache_T analysis
  • IOS explores the nature of methods and the method finding process
  • IOS explores dynamic method resolution and message forwarding mechanisms
  • IOS explores the dyLD loading process briefly
  • The loading process of iOS explore classes
  • IOS explores the loading process of classification and class extension
  • IOS explores isa interview question analysis
  • IOS Explore Runtime interview question analysis
  • IOS explores KVC principles and customization
  • IOS explores KVO principles and customizations
  • IOS explores the principle of multithreading
  • IOS explores multi-threaded GCD applications
  • IOS explores multithreaded GCD underlying analysis
  • IOS explores NSOperation for multithreading
  • IOS explores multi-threaded interview question analysis
  • IOS Explore the locks in iOS
  • IOS explores the full range of blocks to read

Writing in the front

KVC assignment and dictionary model transformation are often used in common development, but what is the underlying principle of KVC?

Demo

First, KVC preliminary study

1.KVC definition and API

Key-value Coding (KVC) is a mechanism implemented using the NSKeyValueCoding informal protocol. Objects use this mechanism to provide indirect access to their attributes

Write down the KVC code and follow setValue and you can see that NSKeyValueCoding is in the Foundation framework

  • KVC byNSObjectExtension to implement — all integratedNSObjectClass can use KVC
  • NSArray, NSDictionary, NSMutableDictionary, NSOrderedSet, NSSetEtc also comply with the KVC protocol
  • KVC can be used for all but a few types (constructs)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FXPerson *person = [FXPerson new];
        [person setValue:@"Felix" forKey:@"name"];
        [person setValue:@"Felix" forKey:@"nickname"];
    }
    return 0;
}
Copy the code

KVC common methods, which we often use in our daily development

// Set the value by key
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// The value is specified by key
- (nullable id)valueForKey:(NSString *)key;
// Set the value to keyPath
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
// The value can be keyPath
- (nullable id)valueForKeyPath:(NSString *)keyPath;
Copy the code

NSKeyValueCoding class other methods

// The default is YES. If the set
      
        method is not found, member variables will be searched _key, _isKey, Key, isKey. If NO is returned, member variables will not be searched
      
+ (BOOL)accessInstanceVariablesDirectly;
// Key-value verification, which can be used to check the correctness of the key-value, and then make the corresponding processing
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// If the key does not exist and no fields related to the key are found, this method is called and an exception is thrown by default. The two methods correspond to get and set cases respectively
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// the method called when the setValue method passes nil
// Note the documentation: this method is called if and only if NSNumber and NSValue are types
- (void)setNilValueForKey:(NSString *)key;
// A set of values for keys that are returned as dictionaries, which can be used to convert models into dictionaries
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
Copy the code

2. Extension — automatically generated setter and getter methods

Imagine a compiler generating setter and getter methods for thousands of properties

As a result, apple developers have used general principles to provide the same entry point for all properties — setter methods in objc-Accessors.mm call different methods based on their modifiers, and finally call reallySetProperty

Go to reallySetProperty and pull out the property based on the memory offset, doing different things based on the modifier

  • In the first propertynameWhen assigned, the memory offset is 8, which is just offsetisaThe amount of memory (8 bytes) comesname
  • In the second attributenicknameWhen assigned, the memory offset is 16, which is just offsetThe isa, the name,The amount of memory (8+8) comes innickname

Where does objc_setProperty_nonatomic_copy get called?

It is not found in objC source code, but in LLVM source code, and you can trace the source layer by layer

Two, KVC use

I believe that most of you reading this article are familiar with the use of KVC, but I suggest you take a look to fill in the gaps

typedef struct {
    float x, y, z;
} ThreeFloats;

@interface FXPerson : NSObject
@property (nonatomic.copy) NSString *name;
@property (nonatomic.assign) NSInteger age;
@property (nonatomic.copy) NSArray  *family;
@property (nonatomic) ThreeFloats threeFloats;
@property (nonatomic.strong) FXFriend *friends;
@end

@interface FXFriend : NSObject
@property (nonatomic.copy) NSString *name;
@property (nonatomic.assign) NSInteger age;
@end
Copy the code

1. Basic types

Notice that when you assign an attribute like NSInteger you convert it to NSNumber or NSString

FXPerson *person = [FXPerson new];

[person setValue:@"Felix" forKey:@"name"];
[person setValue:@(18) forKey:@"age"];
NSLog(Name %@ Age %@, [person valueForKey:@"name"], [person valueForKey:@"age"]);
Copy the code

Print result:

Name: Felix age: 18Copy the code

2. Set types

There are two ways to assign to arrays, and the second method is recommended

FXPerson *person = [FXPerson new];
person.family = @[@"FXPerson".@"FXFather"];

// Assign directly to the new array
NSArray *temp = @[@"FXPerson".@"FXFather".@"FXMother"];
[person setValue:temp forKey:@"family"];
NSLog(@" First change %@", [person valueForKey:@"family"]);

// Take the array and save it as a mutable array, then modify it
NSMutableArray *mTemp = [person mutableArrayValueForKeyPath:@"family"];
[mTemp addObject:@"FXChild"];
NSLog(@" Second change %@", [person valueForKey:@"family"]);
Copy the code

Print result:

913794+0800 FXDemo[2998:151140] first change (FXPerson, FXFather, FXMother) 2020-03-08 14:06:20.913945+0800 FXDemo[2998:151140]Copy the code

3. Access non-object types — structures

  • An assignment of a non-object type is always converted to NSValue before it is stored
  • Change the value to the corresponding type and then use it
FXPerson *person = [FXPerson new];

/ / assignment
ThreeFloats floats = {180.0.180.0.18.0};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSLog(@" Non-object type %@", [person valueForKey:@"threeFloats"]);

/ / value
ThreeFloats th;
NSValue *currentValue = [person valueForKey:@"threeFloats"];
[currentValue getValue:&th];
NSLog(@" Value of non-object type %f-%f-%f", th.x, th.y, th.z);
Copy the code

Print result:

FXDemo[2998:151140] Non-object type {length = 12, Bytes = 0 x000034430000344300009041} 2020-03-08 14:06:20. 914182 + 0800 FXDemo (2998-151140) Non-object type values 180.000000-180.000000-18.000000 2020-03-08 14:06:20.914333+0800 FXDemo[2998:151140] (18, 19, 20, 21, 22, 23)Copy the code

4. Collection operators

  • Aggregate operator

    • @avg: Returns the average value of the specified properties of the operation object
    • @count: Returns the number of specified properties of the operation object
    • @max: Returns the maximum value of the specified property of the operation object
    • @min: Returns the minimum value of the specified property of the operation object
    • @sum: Returns the sum of the specified attribute values of the operation object
  • Array operator

    • @distinctUnionOfObjects: returns a collection of properties specified by the operation object — de-duplication
    • @unionOfObjects: Returns the collection of properties specified by the operation object
  • Nested operators

    • @distinctUnionOfArrays: Returns the set of properties specified by the operation object (nested set) — de-duplicated, returns NSArray
    • @unionOfArrays: Returns the collection of specified properties of the operation object (collection)
    • @distinctUnionOfSets: returns the set of specified attributes of the operation object (nested set) — de-duplicated, returns NSSet

Collection operators are rarely used. Here’s an example 🌰

FXPerson *person = [FXPerson new];

NSMutableArray *friendArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
    FXFriend *f = [FXFriend new];
    NSDictionary* dict = @{
                           @"name":@"Felix".@"age": @ (18+i),
                           };
    [f setValuesForKeysWithDictionary:dict];
    [friendArray addObject:f];
}
NSLog(@ "% @", [friendArray valueForKey:@"age"]);

float avg = [[friendArray valueForKeyPath:@"@avg.age"] floatValue];
NSLog(@" Average age %f", avg);

int count = [[friendArray valueForKeyPath:@"@count.age"] intValue];
NSLog(@" Survey population %d", count);

int sum = [[friendArray valueForKeyPath:@"@sum.age"] intValue];
NSLog(@" Sum of ages %d", sum);

int max = [[friendArray valueForKeyPath:@"@max.age"] intValue];
NSLog(@" Maximum age %d", max);

int min = [[friendArray valueForKeyPath:@"@min.age"] intValue];
NSLog(@" Minimum age %d", min);
Copy the code

Print result:

2020-03-08 14:06:20.914503+0800 FXDemo[2998:151140] Average age 20.500000 2020-03-08 14:06:20.914577+0800 FXDemo[2998:151140] Total age: 123 2020-03-08 14:06:20.914652+0800 FXDemo[2998:151140] Maximum age 23 2020-03-08 14:06:20.914832+0800 FXDemo[2998:151140] minimum age 18Copy the code

5. Layer nesting

Assign values to the instance variable (Friends) using forKeyPath

FXPerson *person = [FXPerson new];

FXFriend *f = [[FXFriend alloc] init];
f.name = Friend of Felix;
f.age = 18;
person.friends = f;
[person setValue:@"Feng" forKeyPath:@"friends.name"];
NSLog(@ "% @", [person valueForKeyPath:@"friends.name"]);
Copy the code

Print result:

The 2020-03-08 14:06:20. 914927 + 0800 FXDemo [2998-151140] FengCopy the code

Third, the underlying principle of KVC

Because NSKeyValueCoding is implemented in the Foundation framework, but it is not open source, we can only learn about it through the official KVO documentation

1. Setting process

The official documentation explains the procedure for Setter methods as follows

  1. Press set

    : and _set

    : to check whether there are corresponding methods in the object

    • The direct call set was found
    • Jump step 2 was not found
  2. Judge accessInstanceVariablesDirectly results

    • If YES is selected_<key>,_is<Key>,<key>,is<Key>Search for member variables in the order of, and assign values if found; If not, go to step 3
    • If the value is NO, go to Step 3
  3. Call setValue: forUndefinedKey:. An exception is thrown by default, but subclasses that inherit from NSObject can override the method to avoid crashes and act accordingly

2. Value selection process

Getter methods are also described in the official documentation

  1. Check whether methods exist on objects in the order of get

    ,

    , is

    , and _< Key>


    • If so, call the getter and perform Step 5
    • If not, go to step 2
  2. Find if there are countOf

    and objectIn

    AtIndex: methods (corresponding to the original method defined by the NSArray class) and

    AtIndexes: methods (corresponding to the NSArray method objectsAtIndexes:)


    • If I find the first one(countOf<Key>), and find at least one of the other two, create a proxy collection object that responds to all of the NSArray methods and return that object (that is, eithercountOf<Key> + objectIn<Key>AtIndex:, eithercountOf<Key> + <key>AtIndexes:, eithercountOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:)
    • If not, go to step 3
  3. Find three methods named countOf

    , enumeratorOf

    , and memberOf

    (corresponding to the original method defined by the NSSet class)


    • If all three methods are found, a response all is createdNSSetMethod, and returns the object
    • If not, go to Step 4
  4. Judge accessInstanceVariablesDirectly

    • If YES is selected_<key>,_is<Key>,<key>,is<Key>Search for member variables in the order they are found
    • If the value is NO, go to Step 6
  5. Determine the value of the fetched attribute

    • Property values are objects that are returned directly
    • Property values are not objects, but can be converted toNSNumberType, the attribute value is converted toNSNumberType returns
    • Property values are not objects and cannot be converted toNSNumberType, the attribute value is converted toNSValueType returns
  6. Call valueForUndefinedKey:. An exception is thrown by default, but subclasses that inherit from NSObject can override the method to avoid crashes and act accordingly

4. Customize KVC

We can customize setter methods and getter methods of KVC according to the value setting process and value process of KVC, but all these are guesses made according to official documents. Custom KVC can only replace system KVC to a certain extent, and the general process is almost the same: Implements the setValue: forUndefinedKey:, valueForUndefinedKey: call, and accessInstanceVariablesDirectly whatever is true is false, can keep two calls

Create a new class of NSObject+FXKVC, open two methods with.h, and introduce.m

  • - (void)fx_setValue:(nullable id)value forKey:(NSString *)key;
  • - (nullable id)fx_valueForKey:(NSString *)key;

1. Customize setter methods

  1. Judge not empty
if (key == nil || key.length == 0) return;
Copy the code
  1. Find ways to do itset<Key>,_set<Key>,setIs<Key>If yes, call it directly
NSString *Key = key.capitalizedString;
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];

if ([self fx_performSelectorWithMethodName:setKey value:value]) {
    NSLog(@ "* * * * * * * * * % @ * * * * * * * * * *",setKey);
    return;
} else if ([self fx_performSelectorWithMethodName:_setKey value:value]) {
    NSLog(@ "* * * * * * * * * % @ * * * * * * * * * *",_setKey);
    return;
} else if ([self fx_performSelectorWithMethodName:setIsKey value:value]) {
    NSLog(@ "* * * * * * * * * % @ * * * * * * * * * *",setIsKey);
    return;
}
Copy the code
  1. Determines whether an instance variable can be assigned directly and calls it if it cannotsetValue:forUndefinedKey:Or throw an exception
NSString *undefinedMethodName = @"setValue:forUndefinedKey:";
IMP undefinedIMP = class_getMethodImplementation([self class].NSSelectorFromString(undefinedMethodName));

if(! [self.class accessInstanceVariablesDirectly]) {
    if (undefinedIMP) {
        [self fx_performSelectorWithMethodName:undefinedMethodName value:value key:key];
    } else {
        @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.".self.NSStringFromSelector(_cmd), key] userInfo:nil];
    }
    return;
}
Copy the code
  1. Find the relevant instance variable and assign it
NSMutableArray *mArray = [self getIvarListName];
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);
   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;
}
Copy the code
  1. callsetValue:forUndefinedKey:Or throw an exception
if (undefinedIMP) {
    [self fx_performSelectorWithMethodName:undefinedMethodName value:value key:key];
} else {
    @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.".self.NSStringFromSelector(_cmd), key] userInfo:nil];
}
Copy the code

Here I have a question: did not realize the setValue: forUndefinedKey: when the current class can response respondsToSelector this approach, but direct performSelector will collapse, so I switched to judge whether the IMP is empty

2. Customize getter methods

  1. Judge not empty
if (key == nil  || key.length == 0) return nil;
Copy the code
  1. Find relevant methodsget<Key>,<key>If you find it, return it-Warc-performSelector-leaksRemove warning)
NSString *Key = key.capitalizedString;
NSString *getKey = [NSString stringWithFormat:@"get%@",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)];
}
#pragma clang diagnostic pop
Copy the code
  1. rightNSArrayPerform operations: SearchcountOf<Key>,objectIn<Key>AtIndexmethods
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(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];
        }
        returnmArray; }}#pragma clang diagnostic pop
Copy the code
  1. Determines whether an instance variable can be assigned directly and calls it if it cannotvalueForUndefinedKey:Or throw an exception
NSString *undefinedMethodName = @"valueForUndefinedKey:";
IMP undefinedIMP = class_getMethodImplementation([self class].NSSelectorFromString(undefinedMethodName));

if(! [self.class accessInstanceVariablesDirectly]) {
    
    if (undefinedIMP) {
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [self performSelector:NSSelectorFromString(undefinedMethodName) withObject:key];
#pragma clang diagnostic pop
    } else {
        @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.".self.NSStringFromSelector(_cmd), key] userInfo:nil]; }}Copy the code
  1. Look for relevant instance variables and return when found
NSMutableArray *mArray = [self getIvarListName];
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);;
}
Copy the code
  1. callvalueForUndefinedKey:Or throw an exception
if (undefinedIMP) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    return [self performSelector:NSSelectorFromString(undefinedMethodName) withObject:key];
#pragma clang diagnostic pop
} else {
    @throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.".self.NSStringFromSelector(_cmd), key] userInfo:nil];
}
Copy the code

3. Encapsulation method

A few of the methods used are briefly encapsulated here

  • fx_performSelectorWithMethodName:value:key:Safely call the method and pass two arguments
- (BOOL)fx_performSelectorWithMethodName:(NSString *)methodName value:(id)value key:(id)key {
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value withObject:key];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}
Copy the code
  • fx_performSelectorWithMethodName:key:Safely call methods and pass parameters
- (BOOL)fx_performSelectorWithMethodName:(NSString *)methodName key:(id)key {
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:key];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}
Copy the code
  • getIvarListNameTake a member variable
- (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

There are also a few unusual tricks in KVC that have been mentioned in previous articles and summarized here

Five, KVC abnormal small skills

1. Tip # 1 — Automatic type conversion

  • An assignment of int is automatically converted to __NSCFNumber
[person setValue:@18 forKey:@"age"];
[person setValue:@ "20" forKey:@"age"];
NSLog(@ % @ - % @ "", [person valueForKey:@"age"], [[person valueForKey:@"age"] class]);
Copy the code
  • Assignment with a struct type type is automatically converted to NSConcreteValue
ThreeFloats floats = {1.0.2.0.3.0};
NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSLog(@ % @ - % @ "", [person valueForKey:@"threeFloats"], [[person valueForKey:@"threeFloats"] class]);
Copy the code

2. Tip 2 — Set null values

Sometimes when setting a null value, you can listen by overriding setNilValueForKey, but the following code prints only once

// set type nil
[person setValue:nil forKey:@"age"];
// Set the NSString type to nil
[person setValue:nil forKey:@"subject"];

@implementation FXPerson

- (void)setNilValueForKey:(NSString *)key {
    NSLog(@" Set %@ to null", key);
}

@end
Copy the code

This is because setNilValueForKey is only valid for NSNumber types

3. Tip 3 — Undefined keys

For undefined key we can rewrite the setValue: forUndefinedKey:, valueForUndefinedKey: to listen

@implementation FXPerson

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(Undefined key -- %@,key);
}

- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(Undefined key -- %@,key);
    return @" undefined key";
}

@end
Copy the code

4. Tip 4 — key value validation

Key value validation, a relatively weak feature, can be expanded to do redirection

NSError *error;
NSString *name = @"Felix";
if(! [person validateValue:&name forKey:@"names" error:&error]) {
    NSLog(@ "% @",error);
}else{
    NSLog(@ "% @", [person valueForKey:@"name"]);
}

@implementation FXPerson

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError {
    if([inKey isEqualToString:@"name"{[])self setValue:[NSString stringWithFormat:@" inside change: %@",*ioValue] forKey:inKey];
        return YES;
    }
    *outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:"%@ is not a property of %@",inKey,self] code:10088 userInfo:nil];
    return NO;
}

@end
Copy the code

Write in the back

We usually use KVC in development, understanding the use and principle of KVC will be of great help to us, specific can download Demo operation