YYModel你們確定很熟悉,其非侵入性,易用性都使得它成爲json-Model的新寵,接下來我們分析下他的原理。html
先看YYClassInfo這個類,他是一個runtime中Class在OC層的封裝,而且解析增長了不少描述,因此想了解YYModel原理必須對runtime有必定了解。json
在runtime層類型實際上是一個結構體objc_class,objc_class中存儲着指向超類的superClass、指向所屬類型的ISA、指向class_rw_t的指針,class_rw_t中存儲着這個類的成員變量列表、屬性列表和方法列表。因此其實咱們是能夠經過runtime的api去讀取類的這些信息的。api
編譯器會以必定規則對類型進行編碼,而且存儲在runtime數據結構中,因此咱們能夠根據規則解析出屬性、成員變量和方法參數的類型 《官方文檔》數組
YYEncodingType 表明的是typeEncoding所表明的類型。經過YYEncodingType YYEncodingGetType(const char *typeEncoding)
方法能夠將字符串轉換成一個表明具體類型的枚舉值。 YYEncodingType不單單表明類型,他是一個按位枚舉,還存儲類一些屬性描述信息。緩存
成員變量在runtime層表現爲Ivar這個類型,經過Ivar能夠讀取變量名,等等信息bash
@interface YYClassIvarInfo : NSObject
//注意這裏用assign修飾了,由於runtime層的數據結構都不歸引用計數管理
@property (nonatomic, assign, readonly) Ivar ivar; ///runtime中成員變量
@property (nonatomic, strong, readonly) NSString * name; // 成員變量名字
@property (nonatomic, assign, readonly) ptrdiff_t offset; ///偏移量
@property (nonatomic, strong, readonly) NSString *typeEncoding; //類型編碼
@property (nonatomic, assign, readonly) YYEncodingType type; //由類型編碼解析出的信息
//解析Ivar信息
-(instancetype)initWithIvar:(Ivar)ivar;
@end
複製代碼
這個類中主要用到的runtime接口有數據結構
ivar_getName() //獲取成員變量名
ivar_getOffset() //獲取偏移量
ivar_getTypeEncoding() //獲取成員變量的類型編碼
複製代碼
methodInfo中包含的信息要多一點app
@interface YYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; //runtime Method數據
@property (nonatomic, assign, readonly) NSString * name; //方法名
@property (nonatomic, assign, readonly) SEL sel; //選擇器
@property (nonatomic, assign, readonly) IMP imp; //函數指針
@property (nonatomic, strong, readonly) NSString * typeEncoding; //方法的類型編碼
@property (nonatomic, strong, readonly) NSString * returnTypeEncoding; //返回值的類型編碼
@property (nonatomic, nullable, strong, readonly) NSArray<NSString *> *argumentTypeEncoding; //參數類型數組,用數組表示
- (instancetype)initWithMethod:(Method)method;
@end
複製代碼
主要用到的runtimeApi有ide
method_getName()
method_getImplementation()
method_getTypeEncoding()
method_copyReturnMethod()
method_getNumberOfArguments() //獲取參數數量
method_copyArgumentType(method,i) //獲取方法第I個參數的類型編碼
複製代碼
咱們先分析一下類的屬性中包含什麼樣的信息,包含了成員變量、遵循的協議、還有描述屬性的關鍵字例如內存管理方面的copy、strong、weak等,還要讀寫的readOnly等。還有默認生成的setter和getter方法。這些都須要解析出來函數
@interface YYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; //runtime中屬性數據
@property (nonatomic, strong, readonly) NSString * name; //屬性名
@property (nonatomic, assign, readonly) YYEncodingType type; //類型枚舉
@property (nonatomic, strong, readonly) NSString * typeEncoding; //類型編碼
@property (nonatomic, strong, readonly) NSString * ivarName; //成員變量名字
@property (nonatomic, nullable, assign, readonly) Class cls; //所屬類型,這裏須要直接解析出來
@property (nonatomic, nullable, strong, readonly) NSArray<NSString *> *protocols; //遵循的協議
@property (nonatomic, assign, readonly) SEL getter; //生成的getter方法
@property (nonatomic, assign, readonly) SEL setter; //生成的setter方法
- (instancetype)initWithProperty:(objc_property_t)property;
@end
複製代碼
屬性的解析過程是這裏面最長的,由於涉及到encoding字符串的解析。好比類型的解析。咱們能夠經過property_copyAttributeList
方法獲取屬性的描述objc_property_attribute_t數據結構大概是一個數組,數組的元素是一個map,並且不用的key對應的是不一樣的含義,好比"T"表明的屬性的類型編碼,"V"表明的是成員變量的名字,等等。咱們着重看一下解析類型和協議的位置。
case 'T'://表明type encoding
if (attrs[i].value){
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
//轉成YYEncodingType
type = YYEncodingGetType(attrs[i].value);
//若是類型是oc對象,把OC對象解析出來,例如:@"NSString"
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length){
NSScanner * scanner = [NSScanner scannerWithString:_typeEncoding];
//先把掃描位置移動到" if (![scanner scanString:@"@\"" intoString:NULL]) continue;
NSString *clsName = nil;
//而後三秒至存在"或者<的位置,這是由於若是遵循了協議typeEncode就是@"NSString<NSCopy>"了 if ([scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]){
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
}
NSMutableArray *protocols = nil;
//若是遵循多個協議typecoding是這樣的@"NSString<NSCopy><NSObject>",因此將掃描位置移動到<位置,循環截取
while ([scanner scanString:@"<" intoString:NULL]) {
NSString * protocol = nil;
if ([scanner scanUpToString:@">" intoString:&protocol]){
if (protocol.length){
if (!protocols) protocols = [NSMutableArray new];
[protocols addObject:protocol];
}
[scanner scanString:@">" intoString:NULL];
}
protocols = protocols;
}
}
}
break;
複製代碼
一個OC類型編碼以後是這樣的以NSString爲例,@"NSString"
,若是遵循了協議是這樣的,@"NSString"若是遵循了多個協議是這樣的@"NSString",因此以上代碼也是依據與此展開的。 咱們再看一下如何獲取的get和set方法
if (_name.length){
if (!_getter){
_getter = NSSelectorFromString(_name);
}
if (!_setter){
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@",[_name substringFromIndex:1].uppercaseString,[_name substringFromIndex:1]]);
}
}
複製代碼
這個很簡單其實就是根據屬性名,首字符大些,而後在前面加上get和set,哈哈,是否是很厲害。
那麼其實上面最重要的三部分都看完了,YYClassInfo就很簡單了
@interface YYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; //所屬類型
@property (nullable, nonatomic, assign, readonly) Class superCls; //超類
@property (nullable, nonatomic, assign, readonly) Class metaCls; //元類
@property (nonatomic, readonly) BOOL isMetal; //是不是元類
@property (nonatomic, strong, readonly) NSString * name; //類名
@property (nullable, nonatomic, strong, readonly) YYClassInfo * superClassInfo; //超類的classInfo
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; //屬性集合,以字典的形式存儲,key是成員變量名
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; //方法集合,以字典形式存儲,key是方法名
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; //屬性名,以字典形式存儲,key是屬性名
//設置須要update類信息。
- (void)setNeedUpdate;
//獲取是否須要update類信息
- (BOOL)needUpdate;
//根據cls獲取解析數據YYClassInfo
+ (nullable instancetype)classInfoWithClass:(Class)cls;
//根據className獲取解析數據YYClassInfo
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end
複製代碼
那麼咱們經過classInfo解析一個類型都通過了哪些過程呢,首先會從緩存中讀取是否有緩存數據,這個緩存是一個靜態全局變量,若是緩存中有判斷是否須要更新類數據,若是須要更新從新解析,若是緩存中沒有數據,那麼解析類數據,而後遞歸解析超類數據,直到超類爲nil,NSObject的superClass就爲nil。這個地方看似須要遞歸不少,可是咱們一般的model都是直接繼承自NSObject的,因此基本就兩次左右。 咱們看一下核心代碼,基本都是調用的runtimeApi
- (void)_update{
_ivarInfos = nil;
_methodInfos = nil;
_propertyInfos = nil;
Class cls = self.cls;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
if (methods){
NSMutableDictionary * methodInfo = [NSMutableDictionary new];
_methodInfos = _methodInfos;
for (unsigned int i = 0; i < methodCount; i++){
YYClassMethodInfo * info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
if (info.name) methodInfo[info.name] = info;
}
free(methods);
}
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList(cls, &propertyCount);
if (properties){
NSMutableDictionary * propertyInfos = [NSMutableDictionary new];
_propertyInfos = propertyInfos;
for (unsigned int i = 0; i<propertyCount; i++){
YYClassPropertyInfo * info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
if (info.name) propertyInfos[info.name] = info;
}
free(properties);
}
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
if (ivars){
NSMutableDictionary * ivarInfos = [NSMutableDictionary new];
_ivarInfos = ivarInfos;
for (unsigned int i = 0; i<ivarCount; i++){
YYClassIvarInfo * info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
if (info.name) ivarInfos[info.name] = info;
}
free(ivars);
}
if (!_ivarInfos) _ivarInfos = @{};
if (!_methodInfos) _methodInfos = @{};
if (!_propertyInfos) _propertyInfos = @{};
_needUpdate = NO;
}
複製代碼
由YYClassInfo咱們能看出做者對runtime的理解之深,經過對runtime類結構的封裝,咱們能夠方便的獲取到一個類的各類信息。json轉model也就沒有那麼難了,關於NSObject+YYModel咱們下一章再說。小弟不才,若有誤區請必定及時指出。