RunTime簡稱運(yùn)行時。OC就是運(yùn)行時機(jī)制踱侣,也就是在運(yùn)行時候的一些機(jī)制又厉,其中最主要的是消息機(jī)制。
對于C語言,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù)败晴,如果調(diào)用未實現(xiàn)的函數(shù)就會報錯。 對于OC語言栽渴,屬于動態(tài)調(diào)用過程尖坤,在編譯的時候并不能決定真正調(diào)用哪個函數(shù),只有在真正運(yùn)行的時候才會根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用闲擦。在編譯階段慢味,OC可以調(diào)用任何函數(shù),即使這個函數(shù)并未實現(xiàn)墅冷,只要聲明過就不會報錯纯路。
作為一個開發(fā)者,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要這是一個我的iOS交流群:776598941寞忿,不管你是小白還是大牛歡迎入駐 驰唬,分享BAT,阿里面試題、面試經(jīng)驗腔彰,討論技術(shù)叫编, 大家一起交流學(xué)習(xí)成長!
二. RunTime消息機(jī)制
消息機(jī)制是運(yùn)行時里面最重要的機(jī)制霹抛,OC中任何方法的調(diào)用搓逾,本質(zhì)都是發(fā)送消息。 使用運(yùn)行時杯拐,發(fā)送消息需要導(dǎo)入框架??并且xcode5之后霞篡,蘋果不建議使用底層方法,如果想要使用運(yùn)行時藕施,需要關(guān)閉嚴(yán)格檢查objc_msgSend的調(diào)用寇损,BuildSetting->搜索msg 改為NO。
下來看一下實例方法調(diào)用底層實現(xiàn)
Person *p = [[Person alloc] init];[p eat];// 底層會轉(zhuǎn)化成//SEL:方法編號裳食,根據(jù)方法編號就可以找到對應(yīng)方法的實現(xiàn)矛市。[p performSelector:@selector(eat)];//performSelector本質(zhì)即為運(yùn)行時,發(fā)送消息诲祸,誰做事情就調(diào)用誰 objc_msgSend(p,@selector(eat));// 帶參數(shù)objc_msgSend(p,@selector(eat:),10);
類方法的調(diào)用底層
// 本質(zhì)是會將類名轉(zhuǎn)化成類對象浊吏,初始化方法其實是在創(chuàng)建類對象而昨。[Person eat];// Person只是表示一個類名,并不是一個真實的對象找田。只要是方法必須要對象去調(diào)用歌憨。// RunTime 調(diào)用類方法同樣,類方法也是類對象去調(diào)用墩衙,所以需要獲取類對象务嫡,然后使用類對象去調(diào)用方法。Class personclass = [Persionclass];[[Persionclass]performSelector:@selector(eat)];// 類對象發(fā)送消息objc_msgSend(personclass,@selector(eat));
**@selector (SEL):是一個SEL方法選擇器漆改。**SEL其主要作用是快速的通過方法名字查找到對應(yīng)方法的函數(shù)指針心铃,然后調(diào)用其函數(shù)。SEL其本身是一個Int類型的地址挫剑,地址中存放著方法的名字去扣。 對于一個類中。每一個方法對應(yīng)著一個SEL樊破。所以一個類中不能存在2個名稱相同的方法愉棱,即使參數(shù)類型不同,因為SEL是根據(jù)方法名字生成的哲戚,相同的方法名稱只能對應(yīng)一個SEL奔滑。
運(yùn)行時發(fā)送消息的底層實現(xiàn)每一個類都有一個方法列表 Method List,保存這類里面所有的方法惫恼,根據(jù)SEL傳入的方法編號找到方法档押,相當(dāng)于value - key的映射。然后找到方法的實現(xiàn)祈纯。去方法的實現(xiàn)里面去實現(xiàn)。如圖所示叼耙。
那么內(nèi)部是如何動態(tài)查找對應(yīng)的方法的腕窥?首先我們知道所有的類中都繼承自NSObject類,在NSObjcet中存在一個Class的isa指針筛婉。
typedefstructobjc_class*Class;@interface NSObject {? ? Class isa? OBJC_ISA_AVAILABILITY;}
我們來到objc_class中查看簇爆,其中包含著類的一些基本信息。
structobjc_class{? Class isa;// 指向metaclassClass super_class ;// 指向其父類constchar*name ;// 類名long version ;// 類的版本信息爽撒,初始化默認(rèn)為0入蛆,可以通過runtime函數(shù)class_setVersion和class_getVersion進(jìn)行修改、讀取long info;// 一些標(biāo)識信息,如CLS_CLASS (0x1L) 表示該類為普通 class 硕勿,其中包含對象方法和成員變量;CLS_META (0x2L) 表示該類為 metaclass哨毁,其中包含類方法;long instance_size ;// 該類的實例變量大小(包括從父類繼承下來的實例變量);structobjc_ivar_list*ivars;// 用于存儲每個成員變量的地址structobjc_method_list**methodLists ;// 與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲對象方法,如CLS_META (0x2L)源武,則存儲類方法;structobjc_cache*cache;// 指向最近使用的方法的指針扼褪,用于提升效率想幻;structobjc_protocol_list*protocols;// 存儲該類遵守的協(xié)議}
下面我們就以p實例的eat方法來看看具體消息發(fā)送之后是怎么來動態(tài)查找對應(yīng)的方法的。
實例方法?[p eat];?底層調(diào)用?[p performSelector:@selector(eat)];?方法话浇,編譯器在將代碼轉(zhuǎn)化為?objc_msgSend(p, @selector(eat));
在?objc_msgSend?函數(shù)中脏毯。首先通過?p?的?isa?指針找到?p?對應(yīng)的?class?。在?Class?中先去?cache?中通過?SEL?查找對應(yīng)函數(shù)?method?幔崖,如果找到則通過?method?中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行食店。
若?cache?中未找到。再去?methodList?中查找赏寇。若能找到叛买,則將?method?加入到?cache?中,以方便下次查找蹋订,并通過?method?中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行率挣。
若?methodlist?中未找到,則去?superClass?中查找露戒。若能找到椒功,則將?method?加入到?cache中,以方便下次查找智什,并通過?method?中的函數(shù)指針跳轉(zhuǎn)到對應(yīng)的函數(shù)中去執(zhí)行动漾。
三. 使用RunTime交換方法:
當(dāng)系統(tǒng)自帶的方法功能不夠,需要給系統(tǒng)自帶的方法擴(kuò)展一些功能荠锭,并且保持原有的功能時旱眯,可以使用RunTime交換方法實現(xiàn)。這里要實現(xiàn)image添加圖片的時候证九,自動判斷image是否為空删豺,如果為空則提醒圖片不存在。 方法一:使用分類
+ (nullableUIImage*)xx_ccimageNamed:(NSString*)name{// 加載圖片? ? 如果圖片不存在則提醒或發(fā)出異常UIImage*image = [UIImageimageNamed:name];if(image ==nil) {NSLog(@"圖片不存在");? ? }returnimage;}
缺點(diǎn):每次使用都需要導(dǎo)入頭文件愧怜,并且如果項目比較大呀页,之前使用的方法全部需要更改。
方法二 :RunTime交換方法交換方法的本質(zhì)其實是交換兩個方法的實現(xiàn)拥坛,即調(diào)換xx_ccimageNamed和imageName方法蓬蝶,達(dá)到調(diào)用xx_ccimageNamed其實就是調(diào)用imageNamed方法的目的
那么首先需要明白方法在哪里交換,因為交換只需要進(jìn)行一次猜惋,所以在分類的load方法中丸氛,當(dāng)加載分類的時候交換方法即可。
+(void)load{// 獲取要交換的兩個方法// 獲取類方法? 用Method 接受一下// class :獲取哪個類方法 // SEL :獲取方法編號著摔,根據(jù)SEL就能去對應(yīng)的類找方法缓窜。Method imageNameMethod = class_getClassMethod([UIImageclass],@selector(imageNamed:));// 獲取第二個類方法Method xx_ccimageNameMrthod = class_getClassMethod([UIImageclass],@selector(xx_ccimageNamed:));// 交換兩個方法的實現(xiàn) 方法一 ,方法二。method_exchangeImplementations(imageNameMethod, xx_ccimageNameMrthod);// IMP其實就是 implementation的縮寫:表示方法實現(xiàn)雹洗。}
交換方法內(nèi)部實現(xiàn):
根據(jù)SEL方法編號在Method中找到方法香罐,兩個方法都找到
交換方法的實現(xiàn),指針交叉指向时肿。如圖所示:
注意:交換方法時候 xx_ccimageNamed方法中就不能再調(diào)用imageNamed方法了庇茫,因為調(diào)用imageNamed方法實質(zhì)上相當(dāng)于調(diào)用 xx_ccimageNamed方法,會循環(huán)引用造成死循環(huán)螃成。
RunTime也提供了獲取對象方法和方法實現(xiàn)的方法旦签。
// 獲取方法的實現(xiàn)class_getMethodImplementation(<#__unsafe_unretainedClasscls#>, <#SELname#>)// 獲取對象方法class_getInstanceMethod(<#__unsafe_unretainedClasscls#>, <#SELname#>)
此時,當(dāng)調(diào)用imageNamed:方法的時候就會調(diào)用xx_ccimageNamed:方法寸宏,為image添加圖片宁炫,并判斷圖片是否存在,如果不存在則提醒圖片不存在氮凝。
四. 動態(tài)添加方法
如果一個類方法非常多羔巢,其中可能許多方法暫時用不到。而加載類方法到內(nèi)存的時候需要給每個方法生成映射表罩阵,又比較耗費(fèi)資源竿秆。此時可以使用RunTime動態(tài)添加方法
動態(tài)給某個類添加方法,相當(dāng)于懶加載機(jī)制稿壁,類中許多方法暫時用不到幽钢,那么就先不加載,等用到的時候再去加載方法傅是。
動態(tài)添加方法的方法: 首先我們先不實現(xiàn)對象方法匪燕,當(dāng)調(diào)用performSelector: 方法的時候,再去動態(tài)加載方法喧笔。 這里同上創(chuàng)建Person類帽驯,使用performSelector: 調(diào)用Person類對象的eat方法。
Person *p = [[Person alloc]init];// 當(dāng)調(diào)用 P中沒有實現(xiàn)的方法時溃斋,動態(tài)加載方法[p performSelector:@selector(eat)];
此時編譯的時候是不會報錯的界拦,程序運(yùn)行時才會報錯,因為Person類中并沒有實現(xiàn)eat方法梗劫,當(dāng)去類中的Method List中發(fā)現(xiàn)找不到eat方法,會報錯找不到eat方法截碴。
而當(dāng)找不到對應(yīng)的方法時就會來到攔截調(diào)用梳侨,在找不到調(diào)用的方法程序崩潰之前調(diào)用的方法。 當(dāng)調(diào)用了沒有實現(xiàn)的對象方法的時日丹,就會調(diào)用**?+(BOOL)resolveInstanceMethod:(SEL)sel?方法走哺。 當(dāng)調(diào)用了沒有實現(xiàn)的類方法的時候,就會調(diào)用?+(BOOL)resolveClassMethod:(SEL)sel?**方法哲虾。
首先我們來到API中看一下蘋果的說明丙躏,搜索 Dynamic Method Resolution 來到動態(tài)方法解析择示。
Dynamic Method Resolution的API中已經(jīng)講解的很清晰,我們可以實現(xiàn)方法?resolveInstanceMethod:?或者?resolveClassMethod:?方法晒旅,動態(tài)的給實例方法或者類方法添加方法和方法實現(xiàn)栅盲。
所以通過這兩個方法就可以知道哪些方法沒有實現(xiàn),從而動態(tài)添加方法废恋。參數(shù)sel即表示沒有實現(xiàn)的方法谈秫。
一個objective - C方法最終都是一個C函數(shù),默認(rèn)任何一個方法都有兩個參數(shù)鱼鼓。 self : 方法調(diào)用者 _cmd : 調(diào)用方法編號拟烫。我們可以使用函數(shù)class_addMethod為類添加一個方法以及實現(xiàn)。
這里仿照API給的例子迄本,動態(tài)的為P實例添加eat對象
+(BOOL)resolveInstanceMethod:(SEL)sel{// 動態(tài)添加eat方法// 首先判斷sel是不是eat方法 也可以轉(zhuǎn)化成字符串進(jìn)行比較硕淑。? ? if(sel ==@selector(eat)) {/**
? ? 第一個參數(shù): cls:給哪個類添加方法
? ? 第二個參數(shù): SEL name:添加方法的編號
? ? 第三個參數(shù): IMP imp: 方法的實現(xiàn),函數(shù)入口嘉赎,函數(shù)名可與方法名不同(建議與方法名相同)
? ? 第四個參數(shù): types :方法類型置媳,需要用特定符號,參考API
? ? */class_addMethod(self, sel, (IMP)eat ,"v@:");// 處理完返回YESreturnYES;? ? }return[superresolveInstanceMethod:sel];}
重點(diǎn)來看一下class_addMethod方法
class_addMethod(__unsafe_unretainedClasscls, SELname, IMP imp,constchar *types)
class_addMethod中的四個參數(shù)曹阔。第一半开,二個參數(shù)比較好理解,重點(diǎn)是第三赃份,四個參數(shù)寂拆。
cls : 表示給哪個類添加方法,這里要給Person類添加方法抓韩,self即代表Person纠永。
SEL name : 表示添加方法的編號。因為這里只有一個方法需要動態(tài)添加谒拴,并且之前通過判斷確定sel就是eat方法尝江,所以這里可以使用sel。
IMP imp : 表示方法的實現(xiàn)英上,函數(shù)入口炭序,函數(shù)名可與方法名不同(建議與方法名相同)需要自己來實現(xiàn)這個函數(shù)。每一個方法都默認(rèn)帶有兩個隱式參數(shù)?self : 方法調(diào)用者 _cmd : 調(diào)用方法的標(biāo)號?苍日,可以寫也可以不寫惭聂。
voideat(idself,SEL _cmd){// 實現(xiàn)內(nèi)容NSLog(@"%@的%@方法動態(tài)實現(xiàn)了",self,NSStringFromSelector(_cmd));}
types : 表示方法類型,需要用特定符號相恃。系統(tǒng)提供的例子中使用的是**?"v@:"?辜纲,我們來到API中看看?"v@:"?**指定的方法是什么類型的。
從圖中可以看出
v?-> void 表示無返回值?@?-> object 表示id參數(shù)?:?-> method selector 表示SEL
至此已經(jīng)完成了P實例eat方法的動態(tài)添加。當(dāng)P調(diào)用eat方法時輸出
動態(tài)添加有參數(shù)的方法如果是有參數(shù)的方法耕腾,需要對方法的實現(xiàn)和class_addMethod方法內(nèi)方法類型參數(shù)做一些修改见剩。 方法實現(xiàn):因為在C語言函數(shù)中,所以對象參數(shù)類型只能用id代替扫俺。 方法類型參數(shù):因為添加了一個id參數(shù)苍苞,所以方法類型應(yīng)該為**?"v@:@"?** 來看一下代碼
+(BOOL)resolveInstanceMethod:(SEL)sel{if(sel ==@selector(eat:)) {? ? ? ? class_addMethod(self, sel, (IMP)aaaa ,"v@:@");returnYES;? ? }return[superresolveInstanceMethod:sel];}voidaaaa(idself,SEL _cmd,idNum){// 實現(xiàn)內(nèi)容NSLog(@"%@的%@方法動態(tài)實現(xiàn)了,參數(shù)為%@",self,NSStringFromSelector(_cmd),Num);}
調(diào)用?eat:?函數(shù)
Person *p = [[Person alloc]init];[pperformSelector:@selector(eat:)withObject:@"xx_cc"];
輸出為
五. RunTime動態(tài)添加屬性
使用RunTime給系統(tǒng)的類添加屬性,首先需要了解對象與屬性的關(guān)系牵舵。
對象一開始初始化的時候其屬性name為nil柒啤,給屬性賦值其實就是讓name屬性指向一塊存儲字符串的內(nèi)存,使這個對象的屬性跟這塊內(nèi)存產(chǎn)生一種關(guān)聯(lián)畸颅,個人理解對象的屬性就是一個指針担巩,指向一塊內(nèi)存區(qū)域。
那么如果想動態(tài)的添加屬性没炒,其實就是動態(tài)的產(chǎn)生某種關(guān)聯(lián)就好了涛癌。而想要給系統(tǒng)的類添加屬性,只能通過分類送火。
這里給NSObject添加name屬性拳话,創(chuàng)建NSObject的分類 我們可以使用@property給分類添加屬性
@property(nonatomic,strong)NSString*name;
雖然在分類中可以寫@property 添加屬性,但是不會自動生成私有屬性种吸,也不會生成set,get方法的實現(xiàn)弃衍,只會生成set,get的聲明,需要我們自己去實現(xiàn)坚俗。
方法一:我們可以通過使用靜態(tài)全局變量給分類添加屬性
staticNSString*_name;-(void)setName:(NSString*)name{? ? _name = name;}-(NSString*)name{return_name;}
但是這樣_name靜態(tài)全局變量與類并沒有關(guān)聯(lián)镜盯,無論對象創(chuàng)建與銷毀,只要程序在運(yùn)行_name變量就存在猖败,并不是真正意義上的屬性速缆。
方法二:使用RunTime動態(tài)添加屬性RunTime提供了動態(tài)添加屬性和獲得屬性的方法。
-(void)setName:(NSString*)name{? ? objc_setAssociatedObject(self,@"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}-(NSString*)name{returnobjc_getAssociatedObject(self,@"name");? ? }
動態(tài)添加屬性
objc_setAssociatedObject(idobject,constvoid*key, idvalue, objc_AssociationPolicy policy);
參數(shù)一:?id object?: 給哪個對象添加屬性恩闻,這里要給自己添加屬性艺糜,用self。 參數(shù)二:?void * == id key?: 屬性名幢尚,根據(jù)key獲取關(guān)聯(lián)對象的屬性的值破停,在**?objc_getAssociatedObject?中通過次key獲得屬性的值并返回。 參數(shù)三:?id value?** : 關(guān)聯(lián)的值尉剩,也就是set方法傳入的值給屬性去保存辱挥。 參數(shù)四:?objc_AssociationPolicy policy?: 策略,屬性以什么形式保存边涕。 有以下幾種
typedefOBJC_ENUM(uintptr_t, objc_AssociationPolicy){? ? OBJC_ASSOCIATION_ASSIGN =0,// 指定一個弱引用相關(guān)聯(lián)的對象OBJC_ASSOCIATION_RETAIN_NONATOMIC =1,// 指定相關(guān)對象的強(qiáng)引用,非原子性O(shè)BJC_ASSOCIATION_COPY_NONATOMIC =3,// 指定相關(guān)的對象被復(fù)制,非原子性O(shè)BJC_ASSOCIATION_RETAIN =01401,// 指定相關(guān)對象的強(qiáng)引用功蜓,原子性O(shè)BJC_ASSOCIATION_COPY =01403// 指定相關(guān)的對象被復(fù)制园爷,原子性? };
獲得屬性
objc_getAssociatedObject(idobject,constvoid*key);
參數(shù)一:?id object?: 獲取哪個對象里面的關(guān)聯(lián)的屬性。 參數(shù)二:?void * == id key?: 什么屬性式撼,與**?objc_setAssociatedObject?**中的key相對應(yīng)童社,即通過key值取出value。
此時已經(jīng)成功給NSObject添加name屬性著隆,并且NSObject對象可以通過點(diǎn)語法為屬性賦值扰楼。
NSObject*objc = [[NSObjectalloc]init];objc.name =@"xx_cc";NSLog(@"%@",objc.name);
六. RunTime字典轉(zhuǎn)模型
為了方便以后重用,這里通過給NSObject添加分類美浦,聲明并實現(xiàn)使用RunTime字典轉(zhuǎn)模型的類方法弦赖。
+ (instancetype)modelWithDict:(NSDictionary*)dict
首先來看一下KVC字典轉(zhuǎn)模型和RunTime字典轉(zhuǎn)模型的區(qū)別
KVC:KVC字典轉(zhuǎn)模型實現(xiàn)原理是遍歷字典中所有Key,然后去模型中查找相對應(yīng)的屬性名浦辨,要求屬性名與Key必須一一對應(yīng)蹬竖,字典中所有key必須在模型中存在。 RunTime:RunTime字典轉(zhuǎn)模型實現(xiàn)原理是遍歷模型中的所有屬性名流酬,然后去字典查找相對應(yīng)的Key币厕,也就是以模型為準(zhǔn),模型中有哪些屬性芽腾,就去字典中找那些屬性旦装。
RunTime字典轉(zhuǎn)模型的優(yōu)點(diǎn):當(dāng)服務(wù)器返回的數(shù)據(jù)過多,而我們只使用其中很少一部分時摊滔,沒有用的屬性就沒有必要定義成屬性浪費(fèi)不必要的資源阴绢。只保存最有用的屬性即可。
RunTime字典轉(zhuǎn)模型過程首先需要了解惭载,屬性定義在類里面旱函,那么類里面就有一個屬性列表,屬性列表以數(shù)組的形式存在描滔,根據(jù)屬性列表就可以獲得類里面的所有屬性名棒妨,所以遍歷屬性列表,也就可以遍歷模型中的所有屬性名含长。 所以RunTime字典轉(zhuǎn)模型過程就很清晰了券腔。
創(chuàng)建模型對象
id objc = [[selfalloc]init];
使用**?class_copyIvarList?**方法拷貝成員屬性列表
unsigned int count =0;Ivar *ivarList = class_copyIvarList(self,&count);
參數(shù)一:?__unsafe_unretained Class cls?: 獲取哪個類的成員屬性列表。這里是self拘泞,因為誰調(diào)用分類中類方法纷纫,誰就是self。 參數(shù)二:?unsigned int *outCount?: 無符號int型指針陪腌,這里創(chuàng)建unsigned int型count辱魁,&count就是他的地址烟瞧,保證在方法中可以拿到count的地址為count賦值。傳出來的值為成員屬性總數(shù)染簇。 返回值:?Ivar *?: 返回的是一個Ivar類型的指針 参滴。指針默認(rèn)指向的是數(shù)組的第0個元素,指針+1會向高地址移動一個Ivar單位的字節(jié)锻弓,也就是指向第一個元素砾赔。Ivar表示成員屬性。 3. 遍歷成員屬性列表青灼,獲得屬性列表
for(inti=0;i< count;i++) {? ? ? ? // 獲取成員屬性? ? ? ? Ivar ivar = ivarList[i];}
使用**?ivar_getName(ivar)?**獲得成員屬性名暴心,因為成員屬性名返回的是C語言字符串,將其轉(zhuǎn)化成OC字符串
NSString*propertyName = [NSStringstringWithUTF8String:ivar_getName(ivar)];
通過**?ivar_getTypeEncoding(ivar)?**也可以獲得成員屬性類型杂拨。 5. 因為獲得的是成員屬性名专普,是帶_的成員屬性,所以需要將下劃線去掉扳躬,獲得屬性名脆诉,也就是字典的key。
// 獲取keyNSString *key = [propertyNamesubstringFromIndex:1];
獲取字典中key對應(yīng)的Value贷币。
// 獲取字典的valueidvalue= dict[key];
給模型屬性賦值击胜,并將模型返回
if(value) {// KVC賦值:不能傳空[objc setValue:valueforKey:key];}returnobjc;
至此已成功將字典轉(zhuǎn)為模型。
七. RunTime字典轉(zhuǎn)模型的二級轉(zhuǎn)換
在開發(fā)過程中經(jīng)常用到模型嵌套役纹,也就是模型中還有一個模型偶摔,這里嘗試用RunTime進(jìn)行模型的二級轉(zhuǎn)換,實現(xiàn)思路其實比較簡單清晰促脉。
首先獲得一級模型中的成員屬性的類型
// 成員屬性類型NSString*propertyType = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];
判斷當(dāng)一級字典中的value是字典辰斋,并且一級模型中的成員屬性類型不是NSDictionary的時候才需要進(jìn)行二級轉(zhuǎn)化。?首先value是字典才進(jìn)行轉(zhuǎn)化是必須的瘸味,因為我們通常將字典轉(zhuǎn)化為模型宫仗,其次,成員屬性類型不是系統(tǒng)類旁仿,說明成員屬性是我們自定義的類藕夫,也就是要轉(zhuǎn)化的二級模型。而當(dāng)成員屬性類型就是NSDictionary的話就表明枯冈,我們本就想讓成員屬性是一個字典毅贮,不需要進(jìn)行模型的轉(zhuǎn)換。
idvalue = dict[key];if([value isKindOfClass:[NSDictionaryclass]] && ![propertyType containsString:@"NS"]) {// 進(jìn)行二級轉(zhuǎn)換尘奏。}
獲取要轉(zhuǎn)換的模型類型滩褥,這里需要對propertyType成員屬性類型做一些處理,因為propertyType返回給我們成員屬性類型的是**?@\"Mode\"?炫加,我們需要對他進(jìn)行截取為?Mode?**瑰煎。這里需要注意的是\只是轉(zhuǎn)義符铺然,不占位。
// @\"Mode\"去掉前面的@\"NSRangerange= [propertyType rangeOfString:@"\""];propertyType = [propertyType substringFromIndex:range.location +range.length];// Mode\"去掉后面的\"range= [propertyType rangeOfString:@"\""];propertyType = [propertyType substringToIndex:range.location];
獲取需要轉(zhuǎn)換類的類對象丢间,將字符串轉(zhuǎn)化為類名探熔。
Class modelClass =? NSClassFromString(propertyType);
判斷如果類名不為空則調(diào)用分類的modelWithDict方法,傳value字典烘挫,進(jìn)行二級模型轉(zhuǎn)換,返回二級模型在賦值給value柬甥。
if(modelClass) {value=? [modelClass modelWithDict:value];}
這里可能有些繞饮六,重新理一下,我們通過判斷value是字典并且需要進(jìn)行二級轉(zhuǎn)換苛蒲,然后將value字典轉(zhuǎn)化為模型返回卤橄,并重新賦值給value,最后給一級模型中相對應(yīng)的key賦值模型value即可完成二級字典對模型的轉(zhuǎn)換臂外。
最后附上二級轉(zhuǎn)換的完整方法
+ (instancetype)modelWithDict:(NSDictionary*)dict{// 1.創(chuàng)建對應(yīng)類的對象idobjc = [[selfalloc] init];// count:成員屬性總數(shù)unsignedintcount =0;// 獲得成員屬性列表和成員屬性數(shù)量Ivar *ivarList = class_copyIvarList(self, &count);for(inti =0; i < count; i++) {// 獲取成員屬性Ivar ivar = ivarList[i];// 獲取成員名NSString*propertyName = [NSStringstringWithUTF8String:ivar_getName(ivar)];// 獲取keyNSString*key = [propertyName substringFromIndex:1];// 獲取字典的value key:屬性名 value:字典的值idvalue = dict[key];// 獲取成員屬性類型NSString*propertyType = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];// 二級轉(zhuǎn)換// value值是字典并且成員屬性的類型不是字典,才需要轉(zhuǎn)換成模型if([value isKindOfClass:[NSDictionaryclass]] && ![propertyType containsString:@"NS"]) {// 進(jìn)行二級轉(zhuǎn)換// 獲取二級模型類型進(jìn)行字符串截取窟扑,轉(zhuǎn)換為類名NSRangerange = [propertyType rangeOfString:@"\""];? ? ? ? ? ? propertyType = [propertyType substringFromIndex:range.location + range.length];? ? ? ? ? ? range = [propertyType rangeOfString:@"\""];? ? ? ? ? ? propertyType = [propertyType substringToIndex:range.location];// 獲取需要轉(zhuǎn)換類的類對象Class modelClass =NSClassFromString(propertyType);// 如果類名不為空則進(jìn)行二級轉(zhuǎn)換if(modelClass) {// 返回二級模型賦值給valuevalue =? [modelClass modelWithDict:value];? ? ? ? ? ? }? ? ? ? }if(value) {// KVC賦值:不能傳空[objc setValue:value forKey:key];? ? ? ? }? ? }// 返回模型returnobjc;}
以上只是對RunTime淺顯的理解,足以應(yīng)付iOS面試過程中Runtime的一些問題漏健。
作為一個開發(fā)者嚎货,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要這是一個我的iOS交流群:776598941,不管你是小白還是大牛歡迎入駐 蔫浆,分享BAT,阿里面試題殖属、面試經(jīng)驗,討論技術(shù)瓦盛, 大家一起交流學(xué)習(xí)成長洗显!