1. 寫在前面
之所以一個多星期沒有繼續看的原因是回學校考試了T T,考完回來發現Json to Model框架已經被別人寫完沒我的活了,被安排去做其他的了T T 心疼,雖然飯碗沒了,但是學習還是要繼續的,從之前看YYModel的YYClassInfo就開始慢慢覺得是一層又一層的封裝,接下來我們看看YYModelPropertyMeta的實現
2. NSObject+YYModel 的私有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/// 不同NS型別的Type值 typedef NS_ENUM (NSUInteger, YYEncodingNSType) { YYEncodingTypeNSUnknown = 0, YYEncodingTypeNSString, YYEncodingTypeNSMutableString, YYEncodingTypeNSValue, YYEncodingTypeNSNumber, YYEncodingTypeNSDecimalNumber, YYEncodingTypeNSData, YYEncodingTypeNSMutableData, YYEncodingTypeNSDate, YYEncodingTypeNSURL, YYEncodingTypeNSArray, YYEncodingTypeNSMutableArray, YYEncodingTypeNSDictionary, YYEncodingTypeNSMutableDictionary, YYEncodingTypeNSSet, YYEncodingTypeNSMutableSet, }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/// 從傳進來的class 得到對應的Type值,先判斷是否為Foundation的類 static force_inline YYEncodingNSType YYClassGetNSType(Class cls) { if (!cls) return YYEncodingTypeNSUnknown; if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString; if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString; if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber; if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber; if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue; if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData; if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData; if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate; if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL; if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray; if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray; if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary; if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary; if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet; if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet; return YYEncodingTypeNSUnknown; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/// C 型別下的處理 static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) { switch (type & YYEncodingTypeMask) { case YYEncodingTypeBool: case YYEncodingTypeInt8: case YYEncodingTypeUInt8: case YYEncodingTypeInt16: case YYEncodingTypeUInt16: case YYEncodingTypeInt32: case YYEncodingTypeUInt32: case YYEncodingTypeInt64: case YYEncodingTypeUInt64: case YYEncodingTypeFloat: case YYEncodingTypeDouble: case YYEncodingTypeLongDouble: return YES; default: return NO; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/// 從一個id型別中 解析初NSNumber static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) { static NSCharacterSet *dot; static NSDictionary *dic; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 單例 這個對映表只生成一次 dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)]; dic = @{@"TRUE" : @(YES), @"True" : @(YES), @"true" : @(YES), @"FALSE" : @(NO), @"False" : @(NO), @"false" : @(NO), @"YES" : @(YES), @"Yes" : @(YES), @"yes" : @(YES), @"NO" : @(NO), @"No" : @(NO), @"no" : @(NO), @"NIL" : (id)kCFNull, @"Nil" : (id)kCFNull, @"nil" : (id)kCFNull, @"NULL" : (id)kCFNull, @"Null" : (id)kCFNull, @"null" : (id)kCFNull, @"(NULL)" : (id)kCFNull, @"(Null)" : (id)kCFNull, @"(null)" : (id)kCFNull, @"<NULL>" : (id)kCFNull, @"<Null>" : (id)kCFNull, @"<null>" : (id)kCFNull}; }); if (!value || value == (id)kCFNull) return nil; if ([value isKindOfClass:[NSNumber class]]) return value; if ([value isKindOfClass:[NSString class]]) { NSNumber *num = dic[value]; if (num) { if (num == (id)kCFNull) return nil; return num; } if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) { const char *cstring = ((NSString *)value).UTF8String; if (!cstring) return nil; double num = atof(cstring); if (isnan(num) || isinf(num)) return nil; return @(num); } else { const char *cstring = ((NSString *)value).UTF8String; if (!cstring) return nil; return @(atoll(cstring)); } } return nil; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** 將NSDate轉成ISO標準格式 */ static force_inline NSDateFormatter *YYISODateFormatter() { static NSDateFormatter *formatter = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; }); return formatter; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/// 通過keyPaths 從 dict中取得對應的value static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) { id value = nil; for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) { value = dic[keyPaths[i]]; if (i + 1 < max) { if ([value isKindOfClass:[NSDictionary class]]) { dic = value; } else { return nil; } } } return value; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 從Dic中獲得對應key的值 static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) { id value = nil; for (NSString *key in multiKeys) { if ([key isKindOfClass:[NSString class]]) { value = dic[key]; if (value) break; } else { value = YYValueForKeyPath(dic, (NSArray *)key); if (value) break; } } return value; } |
這幾個方法提供了一系列判斷以及轉換的方法,將對接下來各個類的使用產生巨大的作用。
3. YYModelPropertyMeta 對propertyInfo資訊描述的封裝
我們可以看到這裡再一次對YYClassPropertyInfo進行封裝,可能一開始還看不出這裡一層又一層的封裝的目的,不要急,看完這個例子大概就知道為什麼要這麼做了,我們先來看一下.h檔案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// 建立YYModelProperty的元類 @interface _YYModelPropertyMeta : NSObject { @package // property 名 NSString *_name; // property 編碼 YYEncodingType _type; // Foundation型別 YYEncodingNSType _nsType; // 是否為基礎資料型別 BOOL _isCNumber; // Class Class _cls; // 是否為集合型別即Array/Set/Dicitinoary Class _genericCls; // 屬性的get方法和set方法 SEL _getter; SEL _setter; // 屬性是否提供KVC方法 BOOL _isKVCCompatible; // 屬性是否支援歸檔 BOOL _isStructAvailableForKeyedArchiver; // 是否有自定義的對映字典 BOOL _hasCustomClassFromDictionary; // json 與屬性對映的key 如 @{@"name":@"user"} NSString *_mappedToKey; // json 與屬性對映的key是一個路徑 @{@"name":@"person.name"} NSArray *_mappedToKeyPath; // json 與屬性對映的key是一個陣列,即一個key對應多個json key @{@"name":@[@"name",@"user",@"account"]} NSArray *_mappedToKeyArray; // 描述的property YYClassPropertyInfo *_info; // 在多個屬性對映一個json key 的時候使用 _YYModelPropertyMeta *_next; } @end |
這裡可以看到是將YYClassPropertyInfo增加了一些描述資訊,如_genericCls,_hasCustomClassFromDictionary等資訊進行了封裝,這裡有個比較神奇的地方就是有個next指標,這個指標是不是看起來很熟悉,我們再看看這樣的一個結構:
1 2 3 4 |
struct ListNode{ int m_value; ListNode *next; }; |
這不是我們之前學連結串列的時候的結構嗎? 是的,這裡用了一個連結串列,用了一個next指標將多個屬性名對映同一個json key連線起來。EXO?什麼叫多個屬性名對映同一個json key? 我們先來了解一下json key的幾種情況:
- json key 和 property 1對1
1 2 3 |
{ @"name" : @"name" } |
這種就是經常見到的一個屬性對應一個key的情況
- json key 和 property 多對1
1 2 3 |
{ @"name" : @[@"name",@"username",@"user"] } |
這種情況下,name,username,和user的值都將對映到屬性name上
2.json key 是一個路徑
1 2 3 |
{ @"name":@"user.name" } |
以上三種就是我們經常能看到的關於json key的模式
接下來我們談談多個property對應一個json key的情況,很可能對映表是這樣的:
1 2 3 4 5 |
{ @"name" : @"name", @"fullName" : @"name", @"username" : @"name", } |
在這裡name、fullName、username都對應json中name這個欄位,我們拿剛剛這個json key 對映來做分析
- 首先name最先得到對映,對mapKey進行賦值,取得json中的name欄位進行賦值一系列操作,此時next指標為null
- fullName接著進行對映,對mapKey進行賦值,接著取得原來json key對應的屬性描述物件,將fullName的next指標,指向前一個key對映的物件。
- username接著進行對映,對mapKey進行賦值,接著取得原來json key對應的屬性描述物件,將username的next指標,指向前一個key對映的物件fullName。
就是如下的一個關係:
這樣做有什麼用呢,我們可以看到這樣用了一個連結串列把所有對映到同一個json key的屬性串聯起來,這樣如果json key 欄位裡的值改了,我們可以看到最先得到對映的是name屬性,有了這樣一個表,其他幾個屬性只需要通過next指標就可以拿到對應jsonkey 修改了的值,這樣是不是直接把需要修改的都串聯起來了呢(初步解讀是這樣,如果還有其他作用,後面會做修改)
接下來我們看一下YYModelPropertyMeta實現檔案裡的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
@implementation _YYModelPropertyMeta + (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic { // 建立一個meta物件 _YYModelPropertyMeta *meta = [self new]; // 將屬性名賦值 meta->_name = propertyInfo.name; // 賦值編碼型別 meta->_type = propertyInfo.type; // 將描述屬性賦值 meta->_info = propertyInfo; // 記錄屬性為容器型別的時候 元素的對映型別 meta->_genericCls = generic; if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) { // 先匹配是否為NS型別 即Foundation 型別 meta->_nsType = YYClassGetNSType(propertyInfo.cls); } else { // 是否為C資料型別 meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type); } // 屬性為結構體型別 if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) { /* It seems that NSKeyedUnarchiver cannot decode NSValue except these structs: */ static NSSet *types = nil; static dispatch_once_t onceToken; // 單例 建立一份c結構體型別對映 dispatch_once(&onceToken, ^{ NSMutableSet *set = [NSMutableSet new]; // 32 bit [set addObject:@"{CGSize=ff}"]; [set addObject:@"{CGPoint=ff}"]; [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"]; [set addObject:@"{CGAffineTransform=ffffff}"]; [set addObject:@"{UIEdgeInsets=ffff}"]; [set addObject:@"{UIOffset=ff}"]; // 64 bit [set addObject:@"{CGSize=dd}"]; [set addObject:@"{CGPoint=dd}"]; [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"]; [set addObject:@"{CGAffineTransform=dddddd}"]; [set addObject:@"{UIEdgeInsets=dddd}"]; [set addObject:@"{UIOffset=dd}"]; types = set; }); // 只有以上的結構體才能被歸檔 if ([types containsObject:propertyInfo.typeEncoding]) { meta->_isStructAvailableForKeyedArchiver = YES; } } // 設定class型別 meta->_cls = propertyInfo.cls; // 如果是容器型別 if (generic) { // 從容器class 中讀取 meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)]; } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) { // 從class型別中讀取 meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)]; } // getter 和 setter 方法 if (propertyInfo.getter) { if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) { meta->_getter = propertyInfo.getter; } } if (propertyInfo.setter) { if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) { meta->_setter = propertyInfo.setter; } } /** * 只有實現了getter和setter方法 才能實現歸檔 */ if (meta->_getter && meta->_setter) { /* KVC中不支援的型別 long double 指標物件 SEL等 */ switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: case YYEncodingTypeInt8: case YYEncodingTypeUInt8: case YYEncodingTypeInt16: case YYEncodingTypeUInt16: case YYEncodingTypeInt32: case YYEncodingTypeUInt32: case YYEncodingTypeInt64: case YYEncodingTypeUInt64: case YYEncodingTypeFloat: case YYEncodingTypeDouble: case YYEncodingTypeObject: case YYEncodingTypeClass: case YYEncodingTypeBlock: case YYEncodingTypeStruct: case YYEncodingTypeUnion: { meta->_isKVCCompatible = YES; } break; default: break; } } return meta; } @end |
4. 最後
堅持每天讀原始碼計劃繼續開啟,在學原始碼的過程中我發現很多可以吸收的東西,不斷的一個查缺補漏的過程,很多東西自己沒想到的通過讀原始碼都能學習到,就按毛主席說的“不會的那就抄一遍”,不會的東西那就讀多幾遍,願共勉。