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 by
NSObject
Extension to implement — all integratedNSObject
Class can use KVC NSArray, NSDictionary, NSMutableDictionary, NSOrderedSet, NSSet
Etc 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 property
name
When assigned, the memory offset is 8, which is just offsetisa
The amount of memory (8 bytes) comesname
- In the second attribute
nickname
When 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
-
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
-
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
- If YES is selected
-
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
-
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
-
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
- If I find the first one
-
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 created
NSSet
Method, and returns the object - If not, go to Step 4
- If all three methods are found, a response all is created
-
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
- If YES is selected
-
Determine the value of the fetched attribute
- Property values are objects that are returned directly
- Property values are not objects, but can be converted to
NSNumber
Type, the attribute value is converted toNSNumber
Type returns - Property values are not objects and cannot be converted to
NSNumber
Type, the attribute value is converted toNSValue
Type returns
-
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
- Judge not empty
if (key == nil || key.length == 0) return;
Copy the code
- Find ways to do it
set<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
- Determines whether an instance variable can be assigned directly and calls it if it cannot
setValue: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
- 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
- call
setValue: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
- Judge not empty
if (key == nil || key.length == 0) return nil;
Copy the code
- Find relevant methods
get<Key>
,<key>
If you find it, return it-Warc-performSelector-leaks
Remove 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
- right
NSArray
Perform operations: SearchcountOf<Key>
,objectIn<Key>AtIndex
methods
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
- Determines whether an instance variable can be assigned directly and calls it if it cannot
valueForUndefinedKey:
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
- 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
- call
valueForUndefinedKey:
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
getIvarListName
Take 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