寫在前面,對runtime的學(xué)習(xí),已經(jīng)悄悄地進(jìn)行了很長時(shí)間,知識(shí)結(jié)構(gòu),也由之前的模模糊糊,變得越來越清晰,網(wǎng)上已經(jīng)有很多關(guān)于runtime的學(xué)習(xí)研究篇章了,但是,別人的東西,不是我的東西(包括我的東西,也不是你的東西,如果你看了我的文章,對你有一點(diǎn)啟發(fā)就好,別的不多求),每個(gè)人都會(huì)自己的學(xué)習(xí)記憶體系,把知識(shí)轉(zhuǎn)變?yōu)樽约菏煜さ奶茁?那,在日后回看的時(shí)候,也會(huì)覺得,還是熟悉的味道,上手,也會(huì)極快滴.
1.runtime是什么?(俗一點(diǎn)的說法:說說你對runtime的理解.)
runtime,國內(nèi)叫運(yùn)行時(shí),是一套底層的C語言API,objective-c代碼,底層都是基于它來實(shí)現(xiàn)的.
感受一下發(fā)送消息的代碼:
#import "ViewController.h"
#import "Person.h"http://項(xiàng)目中創(chuàng)建好的一個(gè)Person類,它有一個(gè)working方法
#import <objc/message.h>
- (void)viewDidLoad {
[super viewDidLoad];
//Person *p = [Person alloc];
//查找build setting -> 搜索msg->設(shè)置為NO,才會(huì)有msg代碼提醒
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
//p = [p init];
p = objc_msgSend(p, sel_registerName("init"));
//[p working];
objc_msgSend(p, @selector(working));
//只有對象才能發(fā)消息,所以發(fā)消息的前綴用obj
}
2.運(yùn)行時(shí)和編譯時(shí)特性的對比.
(知識(shí)儲(chǔ)備)源碼的解釋流程:預(yù)編譯->編譯->鏈接->運(yùn)行
編譯:C語言在編譯階段就要切確知道被調(diào)用函數(shù)的真實(shí)類型了,如果在編譯時(shí)不能確定真實(shí)類型,則會(huì)報(bào)錯(cuò).
運(yùn)行時(shí):OC在調(diào)用方法/定義類/定義成員變量時(shí),在編譯時(shí)還是不能知道他們的真實(shí)類型.OC把這一切行為推遲到運(yùn)行時(shí).也就是意味著,有類型不匹配的情況,在運(yùn)行時(shí)才會(huì)拋出異常.OC中調(diào)用方法,也叫消息發(fā)送.注意區(qū)別C語言中的函數(shù)調(diào)用.
注意:編碼時(shí),報(bào)錯(cuò)信息盡量在編譯階段就調(diào)試出來.
3.runtime的相關(guān)術(shù)語
SEL :方法選擇器,他對應(yīng)方法的名字,OC中方法的名字包括冒號.
id :指向某個(gè)類的實(shí)例的指針.
Class :指向 objc_class 結(jié)構(gòu)體的指針.
Method :代表類中的某個(gè)方法,它存儲(chǔ)了方法名(SEL),方法類型(參數(shù)類型和返回值類型),方法實(shí)現(xiàn)(IMP).
IMP :指向函數(shù)的指針,代表了方法的實(shí)現(xiàn).
Cache : 專門用來緩存方法的實(shí)現(xiàn)的(IMP).
Property :屬性
4.OC中的隱形參數(shù)
開發(fā)中,我們經(jīng)常會(huì)用到一個(gè)全局self,但是你沒有想過他是怎么來的呢?其實(shí),這個(gè)self,是每個(gè)方法中都帶有的隱形參數(shù),跟他一起的,還有一個(gè)_cmd參數(shù),他是SEL類型的變量,這兩個(gè)參數(shù),都是蘋果在運(yùn)行時(shí),悄悄咪咪地添加進(jìn)去的.
有關(guān)這兩個(gè)隱形參數(shù):
①觀察這兩個(gè)表達(dá)式typedef id (*IPM) (id, SEL,...)和objc_msgSend(id, SEL,...),我們可以確定,一組id和SEL參數(shù),就能確定唯一的方法實(shí)現(xiàn)地址,相反,一個(gè)確定的方法,也只有唯一的一組id和SEL 參數(shù).讀到這里,你心中的這個(gè)謎團(tuán),有沒有解開了呢?那就是:OC的一個(gè)類中,不能有同名的方法.
②開發(fā)中,除了上面點(diǎn)出的兩個(gè)變量之外,你還用過哪些變量感覺"情不知何起,而一往情深的"? 看代碼,說出你心中的執(zhí)行結(jié)果.
@implementation Dog : Animal
- (instancetype)init{
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
//打印結(jié)果
NSLog(@"%@", NSStringFromClass([self class])); =>Dog
NSLog(@"%@", NSStringFromClass([super class])); =>Dog
很多人會(huì)想當(dāng)然的認(rèn)為“ super 和self類似,應(yīng)該是指向父類的指針吧!”。這是很普遍的一個(gè)誤區(qū)。其實(shí)super 是一個(gè) Magic Key Word, 它本質(zhì)是一個(gè)編譯器標(biāo)示符,和 self是指向的同一個(gè)消息接受者!他們兩個(gè)的不同點(diǎn)在于:super 會(huì)告訴編譯器,調(diào)用class這個(gè)方法時(shí),要去父類的方法,而不是本類里的。
super使用的理性解釋:當(dāng)super接到消息時(shí),編譯器會(huì)添加一個(gè)objc_super 結(jié)構(gòu)體:
struct objc_super {
id receiver; //receiver仍然是self本身
Class class;//Class是指向objc_class結(jié)構(gòu)體的指針,結(jié)構(gòu)體內(nèi)有指向父類的指針的成員變量,所以規(guī)定了`super`直接找父類方法
}
編譯器將指向self的id指針和class的SEL傳遞給objc_msgSendSuper函數(shù)(參數(shù)又進(jìn)行了一次傳遞,內(nèi)容沒有變哦),而class方法只有在NSObject才能找到,OC底層將class方法轉(zhuǎn)為object_getClass(),緊接著又會(huì)被編譯器將代碼轉(zhuǎn)為objc_msgSend(objc_super->receiver,@selector(class)),因?yàn)?一開始就是self去調(diào)用class方法,打印出來的,也就是Dog,而不是Animal.讀到這,如果不是很理解,那你多讀幾遍,畢竟文字嘛,理解起來是沒那么形象.實(shí)在理解不了,就記住那段"感性的super"吧,開發(fā)中,夠用的了.
5.消息發(fā)送
本來想偷偷懶,用語言描述消息發(fā)送的步驟的,但是,寫著寫著,自己都繞暈了,so,a picture speaks all.

6.動(dòng)態(tài)添加方法
當(dāng)一個(gè)消息被發(fā)送出來后,會(huì)經(jīng)過一系列的查找,其中有一個(gè)步驟就是判斷有沒有動(dòng)態(tài)添加方法.那么runtime提供什么樣的接口給外界進(jìn)行動(dòng)態(tài)添加方法呢?
+ (BOOL)resolveInstanceMethod:(SEL)se//動(dòng)態(tài)對象方法
+ (BOOL)resolveClassMethod:(SEL)sel;//動(dòng)態(tài)類方法
通過重寫上面方法,調(diào)用class_addMethod();函數(shù)來動(dòng)態(tài)添加方法,同時(shí)返回YES即可,如果返回NO,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制.
eg:
#import "ViewController.h"
#import <objc/runtime.h>
@implementation ViewController
//沒有返回值沒參數(shù)類型的函數(shù)
void dynamicMethIMP (id self, SEL _cmd){
NSLog(@"我是動(dòng)態(tài)添加的方法實(shí)現(xiàn)");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(resolveThisMethodDynamically)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(resolveThisMethodDynamically)) {
/**
@param sel __unsafe_unretained Class cls 類的類型(需要添加方法的類)
@param SEL name 方法選擇器(方法的名稱)
@param IMP 方法的實(shí)現(xiàn)
@param const char *types 函數(shù)類型
*/
class_addMethod([self class], sel, (IMP)dynamicMethIMP, "v@");
return YES;
}
return [super resolveClassMethod:sel];
}
@end
7.消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā),前提是沒人要的消息,才會(huì)被轉(zhuǎn)發(fā).我個(gè)人認(rèn)為是runtime比較精彩的部分.
1)重定向
消息轉(zhuǎn)發(fā)之前,runtime系統(tǒng)允許外界替換消息的接受者為其他對象,通過-(id)forwardingTargetForSelector:(SEL)aSelector;該方法不能指定對象為self了.如果沒有指定其他對象來發(fā)送這個(gè)信息,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制.
2)轉(zhuǎn)發(fā)
在轉(zhuǎn)發(fā)機(jī)制里面,執(zhí)行的方法是- (void)forwardInvocation:(NSInvocation *)aInvocation;這個(gè)方法需要傳入一個(gè)NSInvocation對象,這個(gè)對象系統(tǒng)會(huì)自動(dòng)調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;返回一個(gè)方法簽名對象,用來生成NSInvocation對象當(dāng)做參數(shù)傳進(jìn)去使用,所以在重寫forwardInvocation:方法來做各種處理(eg:修改方法實(shí)現(xiàn),修改響應(yīng)對象)的同時(shí)也要重寫methodSignatureForSelector:,否則會(huì)拋異常.
如果沒有實(shí)現(xiàn)forwardInvocation:方法,系統(tǒng)會(huì)調(diào)-(void)doesNotRecognizeSelector:(SEL)aSelector方法,如果外界也沒有實(shí)現(xiàn)這個(gè)方法,那么程序就會(huì)crash.到此,消息轉(zhuǎn)發(fā)就告一段落了.
假如發(fā)送了消息之后,沒有方法實(shí)現(xiàn),那么,有多少次機(jī)會(huì)補(bǔ)救呢?我的理解是3次.
8.方法交換
#import "NSObject+CHLog.h"
#import <objc/runtime.h>
#import "CHTools.h"
@implementation NSObject (CHLog)
+(void)load{
//1.獲取系統(tǒng)的description對象方法類型
Method instanceDescription = class_getInstanceMethod(self, @selector(description));
//2.獲取myLog對象的方法類型
Method ch_instanceDescription = class_getInstanceMethod(self, @selector(myLog));
//3.交換方法
method_exchangeImplementations(instanceDescription, ch_instanceDescription);
//1.獲取系統(tǒng)的description類對象方法類型
Method classDescription = class_getClassMethod(self, @selector(description));
//2.獲取系統(tǒng)的myLog類對象方法類型
Method ch_classDescription = class_getClassMethod(self, @selector(myLog));
//3.交換方法
method_exchangeImplementations(classDescription, ch_classDescription);
}
- (NSString *)myLog{
NSString *str = [NSString stringWithFormat:@"[文件名:%s], " "[函數(shù)名:%s], " "[行號:%d], [時(shí)間:%@]\n打印內(nèi)容:", [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __FUNCTION__, __LINE__, [LXHTools getTodayDetailDateString]] ;
return str;
}
+ (NSString *)myLog{
NSString *str = [NSString stringWithFormat:@"[文件名:%s], " "[函數(shù)名:%s], " "[行號:%d], [時(shí)間:%@]\n打印內(nèi)容:", [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __FUNCTION__, __LINE__, [CHTools getTodayDetailDateString]] ;
return str;
}
@end
介紹方法交換,主要是想大家有一個(gè)思想在,就是在沒有.m文件的情況下,想修改一個(gè)類的方法,除了使用繼承和分類暴力搶先之外,還可以利用runtime來實(shí)現(xiàn).并且runtime有一個(gè)好處就是只需要修改一次就能一勞永逸.你設(shè)想,你接觸一個(gè)很老的項(xiàng)目,而項(xiàng)目的需求是要你在系統(tǒng)的方法上添加新的功能,你難道要為系統(tǒng)的類寫一個(gè)分類,再去每個(gè)使用了該方法的類中去導(dǎo)入頭文件,再手動(dòng)把方法替換?如果你不會(huì)使用方法交換,那寫分類,確實(shí)是一個(gè)解決的方法.
交換方法的實(shí)現(xiàn),其實(shí)就是OC中Method Swizzle的實(shí)踐,除了method_exchangeImplementations,我們還可以利用class_replaceMethod來修改類,利用method_setImplementation來直接設(shè)置某個(gè)方法的IMP,歸根到底,都是偷換了selector的IMP.so far,有沒有覺得runtime非常牛逼,但是,runtime雖好,使用需謹(jǐn)慎啊.
如下面的使用的時(shí)候,調(diào)用description方法已經(jīng)被替換成了調(diào)用我的myLog方法。
//項(xiàng)目中用到description方法的地方都會(huì)偷偷變成我的myLog方法的實(shí)現(xiàn)
- (void)viewDidLoad {
[super viewDidLoad];
// description => myLog 交互這兩個(gè)方法實(shí)現(xiàn)
NSLog(@"%@", [Person description]);
Person *p = [[Person alloc] init];
NSLog(@"%@", [p description]);
}
2016-02-23 16:42:11.599 runtime[56314:6093330] [文件名:NSObject+CHLog.m], [函數(shù)名:+[NSObject(CHLog) myLog]], [行號:38], [時(shí)間:2016-02-23 16:42:11]
打印內(nèi)容:
2016-02-23 16:42:11.600 runtime[56314:6093330] [文件名:NSObject+CHLog.m], [函數(shù)名:-[NSObject(CHLog) myLog]], [行號:33], [時(shí)間:2016-02-23 16:42:11]
打印內(nèi)容:
9.動(dòng)態(tài)添加屬性
在這之前,你是不是也認(rèn)為category中只能添加方法,不能添加屬性?
但是,利用runtime,添加屬性,也變成了可能!下面是我在UIView的分類中添加的一個(gè)字符串nameTag屬性,以后直接通過點(diǎn)語法,可以設(shè)置/獲取控件的字符串tag,設(shè)置控件的tag就不再拘泥于NSInteger了.
#import "UIView+CHFrame.h"
#import <objc/runtime.h>
@implementation UIView (CHFrame)
static char nametag_key;
- (void)setNameTag:(NSString *)NameTag {
objc_setAssociatedObject(self, &nametag_key, NameTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)nameTag {
return objc_getAssociatedObject(self, &nametag_key);
}
- (UIView *)viewWithNameTag:(NSString *)aName {
if (!aName) return nil;
// Is this the right view? 查找view
if ([[self nameTag] isEqualToString:aName])
return self;
// Recurse depth first on subviews;
for (UIView *subview in self.subviews) {
UIView *resultView = [subview viewNamed:aName];
if (resultView) return resultView;
}
// Not found
return nil;
}
- (UIView *)viewNamed:(NSString *)aName {
if (!aName) return nil;
return [self viewWithNameTag:aName];
}
@end
10.字典轉(zhuǎn)模型的安全實(shí)現(xiàn)原理
字典轉(zhuǎn)模型,字典,才需要轉(zhuǎn)成模型(將字典中的key,跟模型的屬性名一一對應(yīng)起來,在開發(fā)中直接通過點(diǎn)語法來取值,會(huì)更方便開發(fā)和利于糾錯(cuò)).
1) 自動(dòng)生成模型屬性
通常,服務(wù)器返回的字段會(huì)比較多,有些字段我們用不上,開發(fā)中,多數(shù)時(shí)候我們都是去字典中一個(gè)一個(gè)找,然后再去模型中定義對應(yīng)的屬性,下面介紹一個(gè)方便設(shè)計(jì)模型的方法--遍歷字典中所有的key,并以@property(nonatomic,strong) NSString *name的形式拼接打印出來,拷貝去使用就可以了.
//給NSDictionary寫一個(gè)分類
#import "NSDictionary+Property.h"
@implementation NSDictionary (Property)
// isKindOfClass:判斷是否是當(dāng)前類或者子類
- (void)createModelPropertyFormatter
{
NSMutableString *formatter = [NSMutableString string];
// 遍歷字典
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
NSString *formatterKind;
if ([value isKindOfClass:[NSString class]]) {
formatterKind = [NSString stringWithFormat:@"@property (nonatomic, copy) NSString *%@;",key];
} else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]) {
formatterKind = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
} else if ([value isKindOfClass:[NSNumber class]]) {
formatterKind = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
} else if ([value isKindOfClass:[NSArray class]]) {
formatterKind = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
} else if ([value isKindOfClass:[NSDictionary class]]) {
formatterKind = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
}
[formatter appendFormat:@"\n%@\n",formatterKind]; //換行
}];
NSLog(@"%@",formatter);
}
@end
//我的經(jīng)驗(yàn):將服務(wù)器獲取到的字段轉(zhuǎn)成plist文件,層級更加清晰,獲取字段中的字典數(shù)組,遍歷數(shù)組,得到單個(gè)的字典,通過調(diào)createModelPropertyFormatter方法,把字典的key統(tǒng)統(tǒng)打印出來,拷貝到模型中,刪除多余的字段,此時(shí),模型已經(jīng)創(chuàng)建好了.
//如果不懂使用這個(gè)快捷方法,可以留言,代碼這里就不貼出來了.
2) KVC--Key Value Coding
蘋果已經(jīng)提供了一個(gè)方法,可以快速進(jìn)行字典轉(zhuǎn)模型了
#import "CHTagItem.h"
@implementation CHTagItem
+ (instancetype)tagWithDict:(NSDictionary *)dict{
CHTagItem *tagItem = [[self alloc] init];
[tagItem setValuesForKeysWithDictionary:dict];
return tagItem;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{}
@end
setValuesForKeysWithDictionary:dict快速賦值
setValue:forUndefinedKey:重寫系統(tǒng)方法(空實(shí)現(xiàn)),以防找不到一一對應(yīng)的字段時(shí)拋異常.
上面那種方法,只適用比較簡單的模型設(shè)計(jì).如果服務(wù)器返回的字段中字典有嵌套關(guān)系,只是簡單地重寫setValue:forUndefinedKey方法使程序是不拋異常,你會(huì)發(fā)現(xiàn),模型設(shè)計(jì)得并不是你想要的結(jié)果.下面舉個(gè):
#import <Foundation/Foundation.h>
#import "CHUserItem.h"
@interface CHCategoryItem : NSObject
@property (nonatomic, strong)NSString *name;
@property(nonatomic, strong)NSString *id;
/** 用戶信息模型的集合*/
@property (nonatomic, strong)NSArray<CHUserItem *> *users;
/**用戶信息的模型*/
@property (nonatomic, strong)CHUserItem *user;
@end
#import <Foundation/Foundation.h>
@interface CHUserItem : NSObject
@property (nonatomic, strong)NSString *screen_name;
@property (nonatomic, strong)NSString *header;
@property (nonatomic, strong)NSString *fans_count;
@end
在調(diào)用setValuesForKeysWithDictionary:方法時(shí),系統(tǒng)流程如下:
1.遍歷模型的屬性,調(diào)屬性的setter方法賦值--setName:
2.如果該屬性沒有setter方法,系統(tǒng)會(huì)去找不帶下劃線的屬性賦值--name.
3.如果該屬性沒有不帶下劃線的屬性,那系統(tǒng)會(huì)去找?guī)聞澗€的屬性去賦值--_name.
4.如果第3步還是沒有找到對應(yīng)的屬性賦值,系統(tǒng)就會(huì)調(diào)setValue:forUndefinedKey拋出異常,告訴外界找不到該字段去賦值.
根據(jù)上面的流程,我們可以通過重寫屬性的setter方法,攔截?cái)?shù)據(jù)處理好了再賦值,這樣才可以保證字典轉(zhuǎn)模型成功.
- (void)setUsers:(NSArray *)users{
//保存字典數(shù)據(jù),這一步是寫setter方法的規(guī)范寫法
_users = users;
//字典轉(zhuǎn)模型
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in users) {
//前提:在CHUserItem模型中已經(jīng)實(shí)現(xiàn)userItemsWithDict:方法
CHUserItem *item = [CHUserItem userItemsWithDict:dict];
[arrayM addObject:item];
}
//保存模型數(shù)據(jù)
_users = arrayM;
}
-(void)setUser:(CHUserItem *)user{
_user = user;
NSDictionary *dict = (NSDictionary *)user;
CHUserItem *item = [CHUserItem userItemsWithDict:dict];
_user = item;
}
3) MJExtension的簡單實(shí)現(xiàn)--runtime的運(yùn)用
蘋果因?yàn)椴恢滥愕哪P褪窃鯓釉O(shè)計(jì)的,所以他只能做到像KVC那樣快速賦值.但是我們自己設(shè)計(jì)好了模型之后,我們可以通過遍歷模型的屬性,去字典中找對應(yīng)的字段去賦值,這樣不但減少了遍歷的次數(shù),而且還排除了找不到未定義的key的錯(cuò)誤.MJExtension的實(shí)現(xiàn)原理便是這樣,下面,我貼出不是那么嚴(yán)謹(jǐn)?shù)腗JExtension實(shí)現(xiàn)代碼來共大家理解.
- (void)test{
// 解析Plist文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"category.plist" ofType:nil];
NSDictionary *categoryDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *array = categoryDict[@"list"];
NSMutableArray *m = [NSMutableArray array];
for (NSDictionary *dict in array) {
CHCategoryItem *categoryItem = [CHCategoryItem modelWithDictionay:dict];
[m addObject:categoryItem];
NSLog(@"%@,%@,%@",categoryItem.name, categoryItem.users[0].screen_name,categoryItem.user);
}
}
//2016-02-27 23:28:07.931 runtime[12395:6880878] 網(wǎng)紅,中二函,<CHUserItem: 0x608000026580>
2016-02-27 23:28:07.932 runtime[12395:6880878] 精品,A號逗比,<CHUserItem: 0x6000000273a0>
2016-02-27 23:28:07.932 runtime[12395:6880878] 搞笑,梁猛搞笑視頻,<CHUserItem: 0x6000000276c0>
2016-02-27 23:28:07.933 runtime[12395:6880878] 創(chuàng)意,小越女simida,<CHUserItem: 0x6080000268a0>
2016-02-27 23:28:07.933 runtime[12395:6880878] 視頻,說方言的王子濤濤,<CHUserItem: 0x608000026bc0>
2016-02-27 23:28:07.933 runtime[12395:6880878] 圖文,阿葩罩爺,<CHUserItem: 0x6000000279e0>
2016-02-27 23:28:07.934 runtime[12395:6880878] 潛力,大哥vip,<CHUserItem: 0x600000027d00>
2016-02-27 23:28:07.934 runtime[12395:6880878] 生活,草莓阿三,<CHUserItem: 0x600000028020>
2016-02-27 23:28:07.934 runtime[12395:6880878] 原創(chuàng),5毛團(tuán)隊(duì),<CHUserItem: 0x600000028340>
#import <Foundation/Foundation.h>
@protocol ClassNameInItemArray <NSObject>
/**
return 字典,key是模型中的名稱,value則是數(shù)組所裝的模型的名稱
*/
+ (NSDictionary *)classNameInItemArray;
@end
@interface NSObject (DictToModel)<ClassNameInItemArray>
/**字典轉(zhuǎn)模型的runtime實(shí)現(xiàn)*/
+ (instancetype)modelWithDictionay:(NSDictionary *)dict;
@end
#import "NSObject+DictToModel.h"
#import <objc/runtime.h>
@implementation NSObject (DictToModel)
// 獲取類里面所有方法
// class_copyMethodList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
// 獲取類里面的Property
// class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
// Ivar:實(shí)例變量 以下劃線開頭,Property:屬性
+ (instancetype)modelWithDictionay:(NSDictionary *)dict
{
id objc = [[self alloc] init];//創(chuàng)建模型對象
unsigned int count = 0; // count:實(shí)例變量個(gè)數(shù)
// 獲取實(shí)例變量的數(shù)組集合
Ivar *ivarLists = class_copyIvarList(self, &count);
// 遍歷數(shù)組
for (int i = 0; i < count; i++) {
Ivar ivar = ivarLists[i]; // 1.取出單個(gè)實(shí)例變量
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 2.獲取實(shí)例變量名字(帶下劃線的)
NSString *key = [name substringFromIndex:1]; // 截串,_name -->name
// 獲取實(shí)例變量類型,下面需要用到的
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 斷點(diǎn)查看可看到type為@\"CHUserItem\" 截串獲取:CHUserItem
type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
type = [type stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 根據(jù)key去服務(wù)器返回的字典中查找對應(yīng)的value
id value = dict[key];
//取出來的value是字典,并且模型屬性類型是自定義的(不是NS開頭)
//@property (nonatomic, strong)CHUserItem *user;類型是自定義的類型
if ([value isKindOfClass:[NSDictionary class]] && ![type hasPrefix:@"NS"]) {
// 字典轉(zhuǎn)換成自定義的模型
Class modelClass = NSClassFromString(type);// 使用映射,得到類對象
if (modelClass) {
value = [modelClass modelWithDictionay:value];
}
}
// 判斷值是否是數(shù)組
if ([value isKindOfClass:[NSArray class]]) {
// 字典數(shù)組轉(zhuǎn)換成模型數(shù)組.
// 校驗(yàn)self對應(yīng)的類中,是否實(shí)現(xiàn)我的協(xié)議方法(該方法的作用請看頭文件里的具體說明)
if ([self respondsToSelector:@selector(classNameInItemArray)]) {
id idSelf = self;// 轉(zhuǎn)換成id類型,就能調(diào)用任何對象的方法
// 獲取數(shù)組中字典對應(yīng)的模型字符串
NSString *type = [idSelf classNameInItemArray][key];
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數(shù)組,生成模型數(shù)組
for (NSDictionary *dict in value) {
// 字典轉(zhuǎn)模型
id model = [classModel modelWithDictionay:dict];
[arrM addObject:model];
}
// 把模型數(shù)組賦值給value
value = arrM;
}
}
// 給模型中屬性賦值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
plist文件的概覽圖如下:

如果你英語水平不錯(cuò),可以點(diǎn)擊runtime官網(wǎng)文檔進(jìn)行自己的學(xué)習(xí)研究.
對文章有什么建議或意見,歡迎糾正.
寫文不易,如果覺得本文章對你有用請點(diǎn)個(gè)喜歡/關(guān)注,謝謝!