歡迎閱讀iOS探索系列(按序閱讀食用效果更加)
寫在前面
平常開發中經常用到KVC賦值取值、字典轉模型,但KVC的底層原理又是怎樣的呢?
一、KVC初探
1.KVC定義及API
KVC(Key-Value Coding)
是利用NSKeyValueCoding
非正式協議實現的一種機制,物件採用這種機制來提供對其屬性的間接訪問
寫下KVC程式碼並點選跟進setValue
會發現NSKeyValueCoding
是在Foundation
框架下
- KVC通過對
NSObject
的擴充套件來實現的——所有整合了NSObject
的類可以使用KVC NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet
等也遵守KVC協議- 除少數型別(結構體)以外都可以使用KVC
int main(int argc, const char * argv[]) {
@autoreleasepool {
FXPerson *person = [FXPerson new];
[person setValue:@"Felix" forKey:@"name"];
[person setValue:@"Felix" forKey:@"nickname"];
}
return 0;
}
複製程式碼
KVC
常用方法,這些也是我們在日常開發中經常用到的
// 通過 key 設值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 通過 key 取值
- (nullable id)valueForKey:(NSString *)key;
// 通過 keyPath 設值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
// 通過 keyPath 取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
複製程式碼
NSKeyValueCoding
類別的其它方法
// 預設為YES。 如果返回為YES,如果沒有找到 set<Key> 方法的話, 會按照_key, _isKey, key, isKey的順序搜尋成員變數, 返回NO則不會搜尋
+ (BOOL)accessInstanceVariablesDirectly;
// 鍵值驗證, 可以通過該方法檢驗鍵值的正確性, 然後做出相應的處理
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// 如果key不存在, 並且沒有搜尋到和key有關的欄位, 會呼叫此方法, 預設丟擲異常。兩個方法分別對應 get 和 set 的情況
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// setValue方法傳 nil 時呼叫的方法
// 注意文件說明: 當且僅當 NSNumber 和 NSValue 型別時才會呼叫此方法
- (void)setNilValueForKey:(NSString *)key;
// 一組 key對應的value, 將其轉成字典返回, 可用於將 Model 轉成字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
複製程式碼
2.擴充——自動生成的setter和getter方法
試想一下編譯器要為成千上萬個屬性分別生成setter
和getter
方法那不得歇菜了嘛
於是乎蘋果開發者們就運用通用原則
給所有屬性都提供了同一個入口——objc-accessors.mm
中setter
方法根據修飾符不同
呼叫不同方法,最後統一呼叫reallySetProperty
方法
來到reallySetProperty
再根據記憶體偏移量取出屬性,根據修飾符完成不同的操作
- 在第一個屬性
name
賦值時,此時的記憶體偏移量為8,剛好偏移isa
所佔記憶體(8位元組)來到name
- 在第二個屬性
nickname
賦值時,此時的記憶體偏移量為16,剛好偏移isa、name
所佔記憶體(8+8)來到nickname
至於是哪裡呼叫的objc_setProperty_nonatomic_copy
?
並不是在objc原始碼中,而在llvm原始碼中發現了它,根據它一層層找上去就能找到源頭
二、KVC使用
相信大部分閱讀本文的小夥伴們都對KVC的使用都比較瞭解了,但筆者建議還是看一下查漏補缺
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
複製程式碼
1.基本型別
注意一下NSInteger這類的屬性賦值時要轉成NSNumber或NSString
FXPerson *person = [FXPerson new];
[person setValue:@"Felix" forKey:@"name"];
[person setValue:@(18) forKey:@"age"];
NSLog(@"名字%@ 年齡%@", [person valueForKey:@"name"], [person valueForKey:@"age"]);
複製程式碼
列印結果:
2020-03-08 14:06:20.913692+0800 FXDemo[2998:151140] 名字Felix 年齡18
複製程式碼
2.集合型別
兩種方法對陣列進行賦值,更推薦使用第二種方法
FXPerson *person = [FXPerson new];
person.family = @[@"FXPerson", @"FXFather"];
// 直接用新的陣列賦值
NSArray *temp = @[@"FXPerson", @"FXFather", @"FXMother"];
[person setValue:temp forKey:@"family"];
NSLog(@"第一次改變%@", [person valueForKey:@"family"]);
// 取出陣列以可變陣列形式儲存,再修改
NSMutableArray *mTemp = [person mutableArrayValueForKeyPath:@"family"];
[mTemp addObject:@"FXChild"];
NSLog(@"第二次改變%@", [person valueForKey:@"family"]);
複製程式碼
列印結果:
2020-03-08 14:06:20.913794+0800 FXDemo[2998:151140] 第一次改變(
FXPerson,
FXFather,
FXMother
)
2020-03-08 14:06:20.913945+0800 FXDemo[2998:151140] 第二次改變(
FXPerson,
FXFather,
FXMother,
FXChild
)
複製程式碼
3.訪問非物件型別——結構體
- 對於非物件型別的賦值總是把它先轉成NSValue型別再進行儲存
- 取值時轉成對應型別後再使用
FXPerson *person = [FXPerson new];
// 賦值
ThreeFloats floats = {180.0, 180.0, 18.0};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSLog(@"非物件型別%@", [person valueForKey:@"threeFloats"]);
// 取值
ThreeFloats th;
NSValue *currentValue = [person valueForKey:@"threeFloats"];
[currentValue getValue:&th];
NSLog(@"非物件型別的值%f-%f-%f", th.x, th.y, th.z);
複製程式碼
列印結果:
2020-03-08 14:06:20.914088+0800 FXDemo[2998:151140] 非物件型別{length = 12, bytes = 0x000034430000344300009041}
2020-03-08 14:06:20.914182+0800 FXDemo[2998:151140] 非物件型別的值180.000000-180.000000-18.000000
2020-03-08 14:06:20.914333+0800 FXDemo[2998:151140] (
18,
19,
20,
21,
22,
23
)
複製程式碼
4.集合操作符
-
聚合操作符
@avg
: 返回操作物件指定屬性的平均值@count
: 返回操作物件指定屬性的個數@max
: 返回操作物件指定屬性的最大值@min
: 返回操作物件指定屬性的最小值@sum
: 返回操作物件指定屬性值之和
-
陣列操作符
@distinctUnionOfObjects
: 返回操作物件指定屬性的集合--去重@unionOfObjects
: 返回操作物件指定屬性的集合
-
巢狀操作符
@distinctUnionOfArrays
: 返回操作物件(巢狀集合)指定屬性的集合--去重,返回的是 NSArray@unionOfArrays
: 返回操作物件(集合)指定屬性的集合@distinctUnionOfSets
: 返回操作物件(巢狀集合)指定屬性的集合--去重,返回的是 NSSet
集合操作符用得少之又少。下面舉個?
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(@"平均年齡%f", avg);
int count = [[friendArray valueForKeyPath:@"@count.age"] intValue];
NSLog(@"調查人口%d", count);
int sum = [[friendArray valueForKeyPath:@"@sum.age"] intValue];
NSLog(@"年齡總和%d", sum);
int max = [[friendArray valueForKeyPath:@"@max.age"] intValue];
NSLog(@"最大年齡%d", max);
int min = [[friendArray valueForKeyPath:@"@min.age"] intValue];
NSLog(@"最小年齡%d", min);
複製程式碼
列印結果:
2020-03-08 14:06:20.914503+0800 FXDemo[2998:151140] 平均年齡20.500000
2020-03-08 14:06:20.914577+0800 FXDemo[2998:151140] 調查人口6
2020-03-08 14:06:20.914652+0800 FXDemo[2998:151140] 年齡總和123
2020-03-08 14:06:20.914739+0800 FXDemo[2998:151140] 最大年齡23
2020-03-08 14:06:20.914832+0800 FXDemo[2998:151140] 最小年齡18
複製程式碼
5.層層巢狀
通過forKeyPath
對例項變數(friends)進行取值賦值
FXPerson *person = [FXPerson new];
FXFriend *f = [[FXFriend alloc] init];
f.name = @"Felix的朋友";
f.age = 18;
person.friends = f;
[person setValue:@"Feng" forKeyPath:@"friends.name"];
NSLog(@"%@", [person valueForKeyPath:@"friends.name"]);
複製程式碼
列印結果:
2020-03-08 14:06:20.914927+0800 FXDemo[2998:151140] Feng
複製程式碼
三、KVC底層原理
由於NSKeyValueCoding
的實現在Foundation
框架,但它又不開源,我們只能通過KVO官方文件來了解它
1.設值過程
官方文件上對Setter方法的過程進行了這樣一段講解
-
按
set<Key>:
、_set<Key>:
順序查詢物件中是否有對應的方法- 找到了直接呼叫設值
- 沒有找到跳轉第2步
-
判斷
accessInstanceVariablesDirectly
結果- 為YES時按照
_<key>
、_is<Key>
、<key>
、is<Key>
的順序查詢成員變數,找到了就賦值;找不到就跳轉第3步 - 為NO時跳轉第3步
- 為YES時按照
-
呼叫
setValue:forUndefinedKey:
。預設情況下會引發一個異常,但是繼承於NSObject
的子類可以重寫該方法就可以避免崩潰並做出相應措施
2.取值過程
同樣的官方文件上也給出了Getter方法的過程
-
按照
get<Key>
、<key>
、is<Key>
、_<key>
順序查詢物件中是否有對應的方法- 如果有則呼叫getter,執行第5步
- 如果沒有找到,跳轉到第2步
-
查詢是否有
countOf<Key>
和objectIn<Key>AtIndex:
方法(對應於NSArray
類定義的原始方法)以及<key>AtIndexes:
方法(對應於NSArray
方法objectsAtIndexes:
)- 如果找到其中的第一個
(countOf<Key>)
,再找到其他兩個中的至少一個,則建立一個響應所有 NSArray方法的代理集合物件,並返回該物件(即要麼是countOf<Key> + objectIn<Key>AtIndex:
,要麼是countOf<Key> + <key>AtIndexes:
,要麼是countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:
) - 如果沒有找到,跳轉到第3步
- 如果找到其中的第一個
-
查詢名為
countOf<Key>
、enumeratorOf<Key>
和memberOf<Key>
這三個方法(對應於NSSet
類定義的原始方法)- 如果找到這三個方法,則建立一個響應所有
NSSet
方法的代理集合物件,並返回該物件 - 如果沒有找到,跳轉到第4步
- 如果找到這三個方法,則建立一個響應所有
-
判斷
accessInstanceVariablesDirectly
- 為YES時按照
_<key>
、_is<Key>
、<key>
、is<Key>
的順序查詢成員變數,找到了就取值 - 為NO時跳轉第6步
- 為YES時按照
-
判斷取出的屬性值
- 屬性值是物件,直接返回
- 屬性值不是物件,但是可以轉化為
NSNumber
型別,則將屬性值轉化為NSNumber
型別返回 - 屬性值不是物件,也不能轉化為
NSNumber
型別,則將屬性值轉化為NSValue
型別返回
-
呼叫
valueForUndefinedKey:
。預設情況下會引發一個異常,但是繼承於NSObject
的子類可以重寫該方法就可以避免崩潰並做出相應措施
四、自定義KVC
根據KVC的設值過程、取值過程,我們可以自定義KVC的setter方法和getter方法,但是這一切都是根據官方文件做出的猜測,自定義KVC只能在一定程度上取代系統KVC,大致流程幾乎一致:實現了 setValue:forUndefinedKey: 、 valueForUndefinedKey: 的呼叫,且 accessInstanceVariablesDirectly 無論為true為false,都能保持兩次呼叫
新建一個NSObject+FXKVC
的分類,.h開放兩個方法,.m引入<objc/runtime.h>
- (void)fx_setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)fx_valueForKey:(NSString *)key;
1.自定義setter方法
- 非空判斷
if (key == nil || key.length == 0) return;
複製程式碼
- 找到相關方法
set<Key>
、_set<Key>
、setIs<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 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;
}
複製程式碼
- 判斷是否能夠直接賦值例項變數,不能的情況下就呼叫
setValue:forUndefinedKey:
或丟擲異常
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;
}
複製程式碼
- 找相關例項變數進行賦值
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;
}
複製程式碼
- 呼叫
setValue:forUndefinedKey:
或丟擲異常
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];
}
複製程式碼
在這裡筆者存在一個疑問:沒有實現setValue:forUndefinedKey:時,當前類可以響應respondsToSelector這個方法,但是直接performSelector會崩潰,所以改用了判斷IMP是否為空
2.自定義getter方法
- 非空判斷
if (key == nil || key.length == 0) return nil;
複製程式碼
- 找相關方法
get<Key>
、<key>
,找到就返回(這裡使用-Warc-performSelector-leaks
消除警告)
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
複製程式碼
- 對
NSArray
進行操作:查詢countOf<Key>
、objectIn<Key>AtIndex
方法
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];
}
return mArray;
}
}
#pragma clang diagnostic pop
複製程式碼
- 判斷是否能夠直接賦值例項變數,不能的情況下就呼叫
valueForUndefinedKey:
或丟擲異常
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];
}
}
複製程式碼
- 找相關例項變數,找到了就返回
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);;
}
複製程式碼
- 呼叫
valueForUndefinedKey:
或丟擲異常
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];
}
複製程式碼
3.封裝的方法
這裡簡單封裝了幾個用到的方法
fx_performSelectorWithMethodName:value:key:
安全呼叫方法及傳兩個引數
- (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;
}
複製程式碼
fx_performSelectorWithMethodName:key:
安全呼叫方法及傳參
- (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;
}
複製程式碼
getIvarListName
取成員變數
- (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;
}
複製程式碼
KVC中還有一些異常小技巧,在前文中已經提及過,這裡再總結一下
五、KVC異常小技巧
1.技巧一——自動轉換型別
- 用int型別賦值會自動轉成__NSCFNumber
[person setValue:@18 forKey:@"age"];
[person setValue:@"20" forKey:@"age"];
NSLog(@"%@-%@", [person valueForKey:@"age"], [[person valueForKey:@"age"] class]);
複製程式碼
- 用結構體型別型別賦值會自動轉成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]);
複製程式碼
2.技巧二——設定空值
有時候在設值時設定空值,可以通過重寫setNilValueForKey
來監聽,但是以下程式碼只有列印一次
// Int型別設定nil
[person setValue:nil forKey:@"age"];
// NSString型別設定nil
[person setValue:nil forKey:@"subject"];
@implementation FXPerson
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"設定 %@ 是空值", key);
}
@end
複製程式碼
這是因為setNilValueForKey
只對NSNumber型別有效
3.技巧三——未定義的key
對於未定義的key我們可以通過重寫setValue:forUndefinedKey:
、valueForUndefinedKey:
來監聽
@implementation FXPerson
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"未定義的key——%@",key);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"未定義的key——%@",key);
return @"未定義的key";
}
@end
複製程式碼
4.技巧四——鍵值驗證
一個比較雞肋的功能——鍵值驗證,可以自行展開做重定向
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:@"裡面修改一下: %@",*ioValue] forKey:inKey];
return YES;
}
*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的屬性",inKey,self] code:10088 userInfo:nil];
return NO;
}
@end
複製程式碼
寫在後面
我們平時開發中經常用到KVC,理解KVC的使用和原理對我們會有很大幫助,具體可以下載Demo操作一下