YYModel的作用就是字典轉模型,在瞭解YYModel前,我們先了解下KVC的知識。
KVC:也稱之鍵值編碼,是一種採用了
NSKeyValueCoding
協議的對象(直接或間接繼承NSObject時會爲基本方法提供默認實現)通過間接訪問其屬性的機制,也就是符合鍵值編碼,對象可通過字符串參數來簡單而統一的消息對其屬性進行尋址,例如valueForKey:
和setValue:forKey:
。在一些特殊情況下,KVC還可以簡化代碼。官方文檔直通車
KVC底層原理
舉例:用鍵值編碼實現數據源方法
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}
或者我們常用的設置textFiled的placeholder顏色
[self.textFiled setValue:[UIColor greenColor] forKeyPath:@"_placeholderLabel.textColor"];
當調用協議的 getter(比如
valueForKey:
), 默認實現根據Accessor Search Patterns
中描述的規則確定爲指定鍵提供值的特定訪問器方法或實例變量. 如果返回值不是對象,getter會使用這個值初始一個NSNumber對象或NSValue(對於結構體)對象替代。同樣setter(比如setValue:forKey:
)通過特定鍵或訪問方法或實例變量時確定的數據類型,如果數據類型不是對象,setter首先會向傳入的值對象發送適當的 <type>Value 消息(intValue)提取基礎數據並存儲該數據。僅限Objective-C,因爲Swift的所有屬性都是對象。
自動包裝和解包不僅限於 NSPoint,NSRange,NSRect,和 NSSize. 也可以是 NSValue 對象。舉例:
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
使用KVC獲取myClass的threeFloats
:默認調用threeFloats的getter
,然後將返回值包裝在NSValue中返回。
NSValue* result = [myClass valueForKey:@"threeFloats"];
當然,我們也可以使用KVC來設置threeFloats的值
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
KVC取值和賦值的過程
首先,我們先根據官方文檔找到Accessor Search Patterns
來查看根據輸入key
的搜索流程。下面是官方文檔的部分說明,具體參考官方文檔
說的就是在getter的時候,先按照get<Key>, <key>, is<Key>, or _<key>
的順序來查找,找到了就直接調用,然後判斷收到的屬性值是一個對象指針直接返回,該值是NSNumber支持的標量類型則用包裝NSNumber返回,否則包裝成 NSValue 對象返回。如果沒有找到則在實例中搜索countOf<Key>, objectIn<Key>AtIndex:(對應於NSArray基本方法)和<key>AtIndexes:(對應於NSArray的objectsAtIndexes)
,如果找到則創建一個響應所有NSArray方法的集合代理對象並返回,如果還是沒有找到則查找countOf<Key>, enumeratorOf<Key>和memberOf<Key>:(對應於 NSSet 類的基本方法)
,如果三個方法都找到,會創建一個響應所有NSSet方法的集合代理對象並返回該方法。如果還沒找到並且接收者對象的accessInstanceVariablesDirectly
(是否開啓間接訪問)返回 YES,則會順序查找_<key>, _is<key>, <key>, is<key>
,如果找到了則返回相應的值(對象指針、NSNumber或者NSValue),如果最後還是沒找到則會執行valueForUndefindKey:
,默認拋出異常,NSObject子類可以重寫。
在setter的時候也是順序查找訪問方法名set<key>, _set<key>
,找到了就則使用輸入值執行,沒找到且accessInstanceVariablesDirectly
返回YES,則按照順序查找_<key>, _is<key>, <key>, is<key>
,如果找到了就執行沒找到則執行setValue:forUndefinedKey:
拋出未定義key的異常,同樣NSObject子類可以重寫。
當沒有對異常進行處理的話,不出意外會崩潰。
在賦值和取值過程中,當value爲空的時候,會執行
setNilValueForKey
,如果Key值不存在則執行setValue:forUndefinedKey
。
KVC也可以用於驗證key或key-path的方法,我們也可以爲屬性提供驗證方法。在響應validateValue:forKey:error:
方法的時候,會查找valudate<Key>:error:
是否實現,如果實現了則根據實現方法的自定義邏輯返回YES或者NO,如果沒實現則系統默認返回YES,NSError用來返回error信息。
KVC異常處理
那在實際運用的時候萬一出現意外,要怎麼規避呢?可以在NSObject的分類做下相應的處理(OC的對象幾乎都可以追溯到NSObject)。
// .h文件代碼
- (void)k_setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)k_valueForKey:(NSString *)key;
// ------------------華麗的分割線----------------------
// .m文件代碼
- (void)k_setValue:(id)value forKey:(NSString *)key {
NSError *error;
BOOL validate = [self validateValue:&value forKey:key error:&error];
NSArray *arr = [self getIvarListName];
if (validate) {
if ([arr containsObject:key]) {
[self setValue:value forKey:key];
}else{
NSLog(@"%@ 不存在變量",key);
}
}
}
- (nullable id)k_valueForKey:(NSString *)key {
if (key == nil || key.length == 0) {
NSLog(@"key爲nil或者空值");
return nil;
}
NSArray *arr = [self getIvarListName];
if ([arr containsObject:key]) {
return [self valueForKey:key];
}
NSLog(@"%@ 不存在變量",key);
return nil;
}
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
if (*ioValue == nil || inKey == nil || inKey.length == 0) {
NSLog(@"value 可能爲nil 或者key爲nil或者空值");
return NO;
}
return YES;
}
- (NSMutableArray *)getIvarListName {
NSMutableArray *mulArr = [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);
[mulArr addObject:ivarName];
}
free(ivars);
return mulArr;
}
記得添加#import <objc/runtime.h>
,在調用k_setValue forKey
或者k_valueForKey
的時候會自動檢測value和key是否有效值,如果不是有效值會拋出
KVC使用
- 字典的使用
NSDictionary* dict = @{
@"oneString":@"one",
@"num":@123,
@"list":@[@1, @2, @3]
};
Person *p = [[Person alloc] init];
// 字典轉模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"%@--%d---%@",p.oneString, p.num, p.list);
// 鍵數組轉模型到字典
NSArray *array = @[@"oneString",@"num"];
NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
NSLog(@"%@",dic);
打印結果如下:
one--123---(
1,
2,
3
)
{
num = 123;
oneString = one;
}
- KVC消息傳遞
NSArray *array = @[@"One",@"Two",@"Three",@"HH"];
NSArray *lenStr= [array valueForKeyPath:@"length"];
NSLog(@"%@",lenStr);// 消息從array傳遞給了string
NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
NSLog(@"%@",lowStr);
// 也支持聚合操作符
int count = [[array valueForKeyPath:@"@count.length"] intValue];
NSLog(@"%d", count);
打印結果:
(
3,
3,
5,
2
)
(
one,
two,
three,
hh
)
4
- 補充
當然了,聚合操作符還有很多,列舉一些可能用到的:
@avg:通過right-key-path讀取集合中每個元素讀取屬性,並轉換爲double類型(nil用0代替),計算他們的平均值,然後返回NSNumber。
@ sum:原理同 '@avg',返回所有元素的和。
@ count:以NSNumber實例返回集合中所有對象的數量,如果有right-key-path忽略。
@ max:通過right-key-path在集合中查找並返回最大的元素。查找會使用由Foundation類(例如 NSNumber) 定義的compare: 方法,所以right-key-path指定的屬性必須是一個對這個消息有意義的響應,查找忽略集合中的nil值。
@min:原理同 '@max',返回最小的元素。
下面的也可以對集合進行操作
@distinctUnionOfObjects:返回對right-key-path指定屬性進行合併操作後的去重數組。
@ unionOfObjects:和distinctUnionOfObjects行爲相似, 但是不會刪除重複對象。
@ distinctUnionOfSets:和distinctUnionOfObjects行爲相似獲取交集。
鍵值模型--YYModel
字典轉模型的大概流程是分析對象,獲取到對象的所有ivar,並將ivar一一賦值。
首先,我們進入YYModel的字典轉模型入口yy_modelWithDictionary
、modelWithJSON
方法,部分代碼展示:
+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
Class cls = [self class];
// 分析cls
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}
NSObject *one = [cls new];
if ([one yy_modelSetWithDictionary:dictionary]) return one;
return nil;
}
在metaWithClass:
方法內部先用Core Foundation的CFMutableDictionaryRef進行緩存已經處理過的key-value,以備下次直接使用,並用dispatch_semaphore_t來確保字典的讀取安全。如果沒有該緩存或者不需要更新則進行創建_YYModelMeta
對象,_YYModelMeta
首先對黑名單和白名單進行處理,再次判斷modelContainerPropertyGenericClass
是否需要對特殊屬性進行替換(按照不同的類型進行遍歷 ),然後開始遍歷自己和父類所有的屬性和方法,生成與數據源相對應的字典映射(具體可網上查找),接着處理_YYModelPropertyMeta,判斷是NSString、NSArray,一切準備好後開始執行yy_modelSetWithDictionary:
方法響應CFArrayApplyFunction
,再次執行ModelSetWithPropertyMetaArrayFunction
,獲取到的value傳入ModelSetValueForProperty
中,通過objc_msgSend發送meta->_setter
來完成屬性值的設置。
switch (meta->_nsType) {
case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == YYEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
}
} else if ([value isKindOfClass:[NSNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSNumber *)value).stringValue :
((NSNumber *)value).stringValue.mutableCopy);
} else if ([value isKindOfClass:[NSData class]]) {
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
} else if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSURL *)value).absoluteString :
((NSURL *)value).absoluteString.mutableCopy);
} else if ([value isKindOfClass:[NSAttributedString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSAttributedString *)value).string :
((NSAttributedString *)value).string.mutableCopy);
}
} break;
該文章爲記錄本人的學習路程,希望能夠幫助大家,知識共享,共同成長,共同進步!!!文章地址:https://www.jianshu.com/p/cce3a7b99c84