導(dǎo)讀:
11、12月注定是不太平的月份撕攒,好多小型互聯(lián)網(wǎng)創(chuàng)業(yè)公司都突然崩塌陡鹃,最近一個(gè)朋友跟我抱怨道,說(shuō)終于感受到了互聯(lián)網(wǎng)的瞬息萬(wàn)變了抖坪,昨天我還在公司敲代碼萍鲸,今天就被通知說(shuō)公司倒閉了,可以不用來(lái)公司了擦俐,然后他開始了為期一個(gè)月的找工作經(jīng)歷脊阴,期間問(wèn)到我下面一道題
1、了解runtime嗎蚯瞧?是什么嘿期?
2、你怎么知道的埋合?
3备徐、對(duì)象如何找到對(duì)應(yīng)方法去調(diào)用的
**于是我總結(jié)了很多網(wǎng)上被問(wèn)到的一些關(guān)于runtime的題目,并做了詳細(xì)的回答甚颂,并在后面補(bǔ)充了我在學(xué)習(xí)runtime時(shí)敲的一些代碼蜜猾,如果想吃透runtime的朋友,可以把后面補(bǔ)充的內(nèi)容好好看完 **
一振诬、你會(huì)被問(wèn)到的關(guān)于runtime筆試題:
1. runtime怎么添加屬性蹭睡、方法等
2. runtime 如何實(shí)現(xiàn) weak 屬性
3. runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址?(分別考慮類方法和實(shí)例方法)
4. 使用runtime Associate方法關(guān)聯(lián)的對(duì)象赶么,需要在主對(duì)象dealloc的時(shí)候釋放么肩豁?
5. _objc_msgForward函數(shù)是做什么的?直接調(diào)用它將會(huì)發(fā)生什么辫呻?
6. 能否向編譯后得到的類中增加實(shí)例變量清钥?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么印屁?
7. 簡(jiǎn)述下Objective-C中調(diào)用方法的過(guò)程(runtime)
8. 什么是method swizzling(俗稱黑魔法)
如果上面的題目你全部答得出來(lái)循捺,那就不要浪費(fèi)時(shí)間,直接return吧雄人,程序猿的時(shí)間很寶貴的
二从橘、解答
1. runtime怎么添加屬性、方法等
- ivar表示成員變量
- class_addIvar
- class_addMethod
- class_addProperty
- class_addProtocol
- class_replaceProperty
2. runtime 如何實(shí)現(xiàn) weak 屬性
首先要搞清楚weak屬性的特點(diǎn)
weak策略表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)础钠。為這種屬性設(shè)置新值時(shí)恰力,設(shè)置方法既不保留新值,也不釋放舊值旗吁。此特質(zhì)同assign類似;然而在屬性所指的對(duì)象遭到摧毀時(shí)踩萎,屬性值也會(huì)清空(nil out)
那么runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
runtime對(duì)注冊(cè)的類很钓,會(huì)進(jìn)行布局香府,會(huì)將 weak 對(duì)象放入一個(gè) hash 表中董栽。用 weak 指向的對(duì)象內(nèi)存地址作為 key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)調(diào)用對(duì)象的 dealloc 方法企孩,假設(shè) weak 指向的對(duì)象內(nèi)存地址是a锭碳,那么就會(huì)以a為key,在這個(gè) weak hash表中搜索勿璃,找到所有以a為key的 weak 對(duì)象擒抛,從而設(shè)置為 nil。
weak屬性需要在dealloc中置nil么
在ARC環(huán)境無(wú)論是強(qiáng)指針還是弱指針都無(wú)需在 dealloc 設(shè)置為 nil 补疑, ARC 會(huì)自動(dòng)幫我們處理
即便是編譯器不幫我們做這些歧沪,weak也不需要在dealloc中置nil
在屬性所指的對(duì)象遭到摧毀時(shí),屬性值也會(huì)清空
objc// 模擬下weak的setter方法莲组,大致如下- (void)setObject:(NSObject *)object{ objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); [object cyl_runAtDealloc:^{ _object = nil; }];}
3. runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址诊胞?(分別考慮類方法和實(shí)例方法)
- 每一個(gè)類對(duì)象中都一個(gè)對(duì)象方法列表(對(duì)象方法緩存)
- 類方法列表是存放在類對(duì)象中isa指針指向的元類對(duì)象中(類方法緩存)
- 方法列表中每個(gè)方法結(jié)構(gòu)體中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型,其實(shí)selector本質(zhì)就是方法名稱,通過(guò)這個(gè)方法名稱就可以在方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn).
- 當(dāng)我們發(fā)送一個(gè)消息給一個(gè)NSObject對(duì)象時(shí)锹杈,這條消息會(huì)在對(duì)象的類對(duì)象方法列表里查找
- 當(dāng)我們發(fā)送一個(gè)消息給一個(gè)類時(shí)厢钧,這條消息會(huì)在類的Meta Class對(duì)象的方法列表里查找
4. 使用runtime Associate方法關(guān)聯(lián)的對(duì)象,需要在主對(duì)象dealloc的時(shí)候釋放么嬉橙?
無(wú)論在MRC下還是ARC下均不需要被關(guān)聯(lián)的對(duì)象在生命周期內(nèi)要比對(duì)象本身釋放的晚很多,它們會(huì)在被 NSObject -dealloc 調(diào)用的object_dispose()方法中釋放
補(bǔ)充:對(duì)象的內(nèi)存銷毀時(shí)間表寥假,分四個(gè)步驟
>1市框、調(diào)用 -release :引用計(jì)數(shù)變?yōu)榱?* 對(duì)象正在被銷毀,生命周期即將結(jié)束.
* 不能再有新的 __weak 弱引用糕韧,否則將指向 nil.
* 調(diào)用 [self dealloc]
>
2枫振、 父類調(diào)用 -dealloc
* 繼承關(guān)系中最直接繼承的父類再調(diào)用 -dealloc
* 如果是 MRC 代碼 則會(huì)手動(dòng)釋放實(shí)例變量們(iVars)
* 繼承關(guān)系中每一層的父類 都再調(diào)用 -dealloc
>3、NSObject 調(diào) -dealloc
* 只做一件事:調(diào)用 Objective-C runtime 中object_dispose() 方法
>4. 調(diào)用 object_dispose()
* 為 C++ 的實(shí)例變量們(iVars)調(diào)用 destructors
* 為 ARC 狀態(tài)下的 實(shí)例變量們(iVars) 調(diào)用 -release
* 解除所有使用 runtime Associate方法關(guān)聯(lián)的對(duì)象
* 解除所有 __weak 引用
* 調(diào)用 free()
5. _objc_msgForward函數(shù)是做什么的萤彩?直接調(diào)用它將會(huì)發(fā)生什么粪滤?
_objc_msgForward是IMP類型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息雀扶,但它并沒有實(shí)現(xiàn)的時(shí)候杖小,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)
直接調(diào)用_objc_msgForward是非常危險(xiǎn)
的事,這是把雙刃刀愚墓,如果用不好會(huì)直接導(dǎo)致程序Crash予权,但是如果用得好,能做很多非忱瞬幔酷的事
JSPatch就是直接調(diào)用_objc_msgForward來(lái)實(shí)現(xiàn)其核心功能的
詳細(xì)解說(shuō)參見這里的第一個(gè)問(wèn)題解答
6. 能否向編譯后得到的類中增加實(shí)例變量扫腺?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么村象?
- 不能向編譯后得到的類中增加實(shí)例變量笆环;
- 能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量攒至;
- 分析如下:
- 因?yàn)榫幾g后的類已經(jīng)注冊(cè)在runtime中,類結(jié)構(gòu)體中的objc_ivar_list 實(shí)例變量的鏈表和instance_size實(shí)例變量的內(nèi)存大小已經(jīng)確定躁劣,同時(shí)runtime 會(huì)調(diào)用class_setIvarLayout 或 class_setWeakIvarLayout來(lái)處理strong weak引用迫吐,所以不能向存在的類中添加實(shí)例變量
- 運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用 class_addIvar函數(shù)习绢,但是得在調(diào)用objc_allocateClassPair之后渠抹,objc_registerClassPair之前,原因同上闪萄。
7. 簡(jiǎn)述下Objective-C中調(diào)用方法的過(guò)程(runtime)
- Objective-C是動(dòng)態(tài)語(yǔ)言梧却,每個(gè)方法在運(yùn)行時(shí)會(huì)被動(dòng)態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)败去,整個(gè)過(guò)程介紹如下:
* objc在向一個(gè)對(duì)象發(fā)送消息時(shí)放航,runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類
* 然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行
* 如果,在最頂層的父類(一般也就NSObject)中依然找不到相應(yīng)的方法時(shí)圆裕,程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX
* 但是在這之前广鳍,objc的運(yùn)行時(shí)會(huì)給出三次拯救程序崩潰的機(jī)會(huì),這三次拯救程序奔潰的說(shuō)明見問(wèn)題《什么時(shí)候會(huì)報(bào)unrecognized selector的異诚抛保》中的說(shuō)明
- 補(bǔ)充說(shuō)明:Runtime 鑄就了Objective-C 是動(dòng)態(tài)語(yǔ)言的特性赊时,使得C語(yǔ)言具備了面向?qū)ο蟮奶匦裕诔绦蜻\(yùn)行期創(chuàng)建行拢,檢查祖秒,修改類、對(duì)象及其對(duì)應(yīng)的方法舟奠,這些操作都可以使用runtime中的對(duì)應(yīng)方法實(shí)現(xiàn)竭缝。
8. 什么是method swizzling(俗稱黑魔法)
- 簡(jiǎn)單說(shuō)就是進(jìn)行方法交換
- 在Objective-C中調(diào)用一個(gè)方法,其實(shí)是向一個(gè)對(duì)象發(fā)送消息沼瘫,查找消息的唯一依據(jù)是selector的名字抬纸。利用Objective-C的動(dòng)態(tài)特性,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對(duì)應(yīng)的方法實(shí)現(xiàn)耿戚,達(dá)到給方法掛鉤的目的
- 每個(gè)類都有一個(gè)方法列表湿故,存放著方法的名字和方法實(shí)現(xiàn)的映射關(guān)系,selector的本質(zhì)其實(shí)就是方法名溅话,IMP有點(diǎn)類似函數(shù)指針晓锻,指向具體的Method實(shí)現(xiàn),通過(guò)selector就可以找到對(duì)應(yīng)的IMP
- 交換方法的幾種實(shí)現(xiàn)方式
- 利用 method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)
- 利用 class_replaceMethod 替換方法的實(shí)現(xiàn)
-
利用 method_setImplementation 來(lái)直接設(shè)置某個(gè)方法的IMP
三飞几、補(bǔ)充(重要)
1砚哆、消息機(jī)制
-
1、方法調(diào)用底層實(shí)現(xiàn)
2、runtime:千萬(wàn)不要隨便使用,不得已才使用
//消息機(jī)制:
//作用:調(diào)用已知私有方法躁锁,如調(diào)用沒有在.h文件申明但是在.m文件實(shí)現(xiàn)了的方法
// 用runtime調(diào)用私有方法:方法編號(hào)后面開始,依次就是傳入給方法的參數(shù)
objc_msgSend(p, @selector(run: str:),20,@"haha");
objc_msgSend(p, @selector(eat));
// [p eat] => objc_msgSend(p, @selector(eat));
-
3纷铣、對(duì)象如何找到對(duì)應(yīng)的方法去調(diào)用
// 方法保存到什么地方?對(duì)象方法保存到類中,類方法保存到元類(meta class),每一個(gè)類都有方法列表methodList
//明確去哪個(gè)類中調(diào)用战转,通過(guò)isa指針
* 1.根據(jù)對(duì)象的isa去對(duì)應(yīng)的類查找方法,isa:判斷去哪個(gè)類查找對(duì)應(yīng)的方法 指向方法調(diào)用的類
* 2.根據(jù)傳入的方法編號(hào)SEL搜立,里面有個(gè)哈希列表,在列表中找到對(duì)應(yīng)方法Method(方法名)
* 3.根據(jù)方法名(函數(shù)入口)找到函數(shù)實(shí)現(xiàn)槐秧,函數(shù)實(shí)現(xiàn)在方法區(qū)
2啄踊、交換方法
- 1、需求:比如我有個(gè)項(xiàng)目,已經(jīng)開發(fā)2年,之前都是使用UIImage去加載圖片,組長(zhǎng)想要在調(diào)用imageNamed,就給我提示,是否加載成功刁标,如果用方法2颠通,每個(gè)調(diào)用imageNamed方法的,都要改成xmg_imageNamed:才能擁有這個(gè)功能膀懈,很麻煩顿锰。解決:用runtime交換方法,即下面方法3
①解決方式 自定義UIImage類启搂,缺點(diǎn):每次用要導(dǎo)入自己的類
②解決方法:UIImage分類擴(kuò)充一個(gè)這樣方法硼控,缺點(diǎn):需要導(dǎo)入,無(wú)法寫super和self胳赌,會(huì)干掉系統(tǒng)方法牢撼,解決:給系統(tǒng)方法加個(gè)前綴,與系統(tǒng)方法區(qū)分疑苫,如:xmg_imageNamed:
③交互方法實(shí)現(xiàn)浪默,步驟: 1.提供分類 2.寫一個(gè)有這樣功能方法 3.用系統(tǒng)方法與這個(gè)功能方法交互實(shí)現(xiàn),在+load方法中實(shí)現(xiàn)
注意:在分類一定不要重寫系統(tǒng)方法,就直接把系統(tǒng)方法干掉缀匕,如果真的想重寫,在系統(tǒng)方法前面加前綴碰逸,方法里面去調(diào)用系統(tǒng)方法
思想:什么時(shí)候需要自定義,系統(tǒng)功能不完善,就自定義一個(gè)這樣類,去擴(kuò)展這個(gè)類
/#import "UIImage+Image.h"
/#import <objc/message.h>
@implementation UIImage (Image)
// 加載類的時(shí)候調(diào)用,肯定只會(huì)調(diào)用一次
+(void)load
{
// 交互方法實(shí)現(xiàn)xmg_imageNamed,imageNamed
/**
獲取類方法名
@param Class cls,#> 獲取哪個(gè)類方法 description#>
@param SEL name#> 方法編號(hào) description#>
@return 返回Method(方法名)
class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
/**
獲取對(duì)象方法名
@param Class cls,#> 獲取哪個(gè)對(duì)象方法 description#>
@param SEL name#> 方法編號(hào) description#>
@return 返回Method(方法名)
class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
Method xmg_imageNameMethod = class_getClassMethod(self, @selector(xmg_imageNamed:));
//用runtime對(duì)imageNameMethod和xmg_imageNameMethod方法進(jìn)行交換
method_exchangeImplementations(imageNameMethod, xmg_imageNameMethod);
}
//外界調(diào)用imageNamed:方法乡小,其實(shí)是調(diào)用下面方法,調(diào)用xmg_imageNamed就是調(diào)用imageNamed:
+ (UIImage *)xmg_imageNamed:(NSString *)name
{
//已經(jīng)把xmg_imageNamed換成imageNamed饵史,所以下面其實(shí)是調(diào)用的imageNamed:
UIImage *image = [UIImage xmg_imageNamed:name];
if (image == nil) {
NSLog(@"加載失敗");
}
return image;
}
@end
3满钟、動(dòng)態(tài)添加方法
動(dòng)態(tài)添加方法:
為什么動(dòng)態(tài)添加方法? OC都是懶加載,有些方法可能很久不會(huì)調(diào)用
應(yīng)用場(chǎng)景:電商,視頻,社交,收費(fèi)項(xiàng)目:會(huì)員機(jī)制中,只要會(huì)員才擁有這些功能
- 1胳喷、美團(tuán)面試題:有沒有使用過(guò)performSelector,使用,什么時(shí)候使用?動(dòng)態(tài)添加方法的時(shí)候使用? 為什么動(dòng)態(tài)添加方法
// 默認(rèn)OC方法都有兩個(gè)默認(rèn)存在的隱式參數(shù),self(哪個(gè)類的方法),_cmd(方法編號(hào))
void run(id self, SEL _cmd, NSNumber *metre) {
NSLog(@"跑了%@",metre);
}
- 2湃番、什么時(shí)候調(diào)用:只要調(diào)用沒有實(shí)現(xiàn)的方法 就會(huì)調(diào)用方法去解決,這里可以拿到那個(gè)未實(shí)現(xiàn)的方法名
// 作用:去解決沒有實(shí)現(xiàn)方法,動(dòng)態(tài)添加方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
class:給誰(shuí)添加方法
SEL:添加哪個(gè)方法
IMP:方法實(shí)現(xiàn),函數(shù)入口,函數(shù)名吭露,如:(IMP)run吠撮,方法名run強(qiáng)轉(zhuǎn)成IMP
type:方法類型,通過(guò)查蘋果官方文檔讲竿,V:void泥兰,
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
// [NSStringFromSelector(sel) isEqualToString:@"eat"];
if (sel == @selector(run:)) {
// 添加方法
class_addMethod(self, sel, (IMP)run,"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
4弄屡、動(dòng)態(tài)添加屬性
- 1、需求:給NSObject添加一個(gè)name屬性,動(dòng)態(tài)添加屬性 -> runtime
思考:
①給NSObject添加分類鞋诗,在分類中添加屬性膀捷。問(wèn)題:@property在分類中作用:僅僅是生成get,set方法聲明,不會(huì)生成get,set方法實(shí)現(xiàn)和下劃線成員屬性削彬,所以要在.m文件實(shí)現(xiàn)setter/getter方法全庸,用static保存下滑線屬性,這樣一來(lái)融痛,當(dāng)對(duì)象銷毀時(shí)壶笼,屬性無(wú)法銷毀
②用runtime動(dòng)態(tài)添加屬性:本質(zhì)是讓屬性與某個(gè)對(duì)象產(chǎn)生一段關(guān)聯(lián)
使用場(chǎng)景:給系統(tǒng)的類添加屬性時(shí)
#import <objc/message.h>
@implementation NSObject (Property)
//static NSString *_name; //通過(guò)這樣去保存屬性沒法做到對(duì)象銷毀,屬性也銷毀酌心,static依然會(huì)讓屬性存在緩存池中拌消,所以需要?jiǎng)討B(tài)的添加成員屬性
// 只要想調(diào)用runtime方法,思考:誰(shuí)的事情
-(void)setName:(NSString *)name
{
// 保存name
// 動(dòng)態(tài)添加屬性 = 本質(zhì):讓對(duì)象的某個(gè)屬性與值產(chǎn)生關(guān)聯(lián)
/*
object:保存到哪個(gè)對(duì)象中
key:用什么屬性保存 屬性名
value:保存值
policy:策略,strong,weak
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
*/
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// _name = name;
}
- (NSString *)name
{
return objc_getAssociatedObject(self, "name");
// return _name;
}
@end
5、自動(dòng)生成屬性代碼
開發(fā)中安券,從網(wǎng)絡(luò)數(shù)據(jù)中解析出字典數(shù)組墩崩,將數(shù)組轉(zhuǎn)為模型時(shí),如果有幾百個(gè)key需要用侯勉,要寫很多@property成員屬性鹦筹,下面提供一個(gè)萬(wàn)能的方法,可直接將字典數(shù)組轉(zhuǎn)為全部@property成員屬性址貌,打印出來(lái)铐拐,這樣直接復(fù)制在模型中就好了
#import "NSDictionary+PropertyCode.h"
@implementation NSDictionary (PropertyCode)
//1??通過(guò)這個(gè)方法,自動(dòng)將字典轉(zhuǎn)成模型中需要用的屬性代碼
// 私有API:真實(shí)存在,但是蘋果沒有暴露出來(lái),不給你练对。如BOOL值遍蟋,不知道類型,打印得知是__NSCFBoolean螟凭,但是無(wú)法敲出來(lái)虚青,只能用NSClassFromString(@"__NSCFBoolean")
// isKindOfClass:判斷下是否是當(dāng)前類或者子類,BOOL是NSNumber的子類螺男,要先判斷BOOL
- (void)createPropetyCode
{
// 模型中屬性根據(jù)字典的key
// 有多少個(gè)key,生成多少個(gè)屬性
NSMutableString *codes = [NSMutableString string];
// 遍歷字典
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
NSString *code = nil;
// NSLog(@"%@",[value class]);
if ([value isKindOfClass:[NSString class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
} else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
} else if ([value isKindOfClass:[NSNumber class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
} else if ([value isKindOfClass:[NSArray class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
} else if ([value isKindOfClass:[NSDictionary class]]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
}
// 拼接字符串
[codes appendFormat:@"%@\n",code];
}];
NSLog(@"%@",codes);
}
@end
6棒厘、KVC字典轉(zhuǎn)模型
// 需求:就是在開發(fā)中,通常后臺(tái)會(huì)給你很多數(shù)據(jù),但是并不是每個(gè)數(shù)據(jù)都有用,這些沒有用的數(shù)據(jù),需不需要保存到模型中
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
// 創(chuàng)建模型
Status *s = [[self alloc] init];
// 字典value轉(zhuǎn)模型屬性保存
[s setValuesForKeysWithDictionary:dict];
// s.reposts_count = dict[@"reposts_count"];
// 4??MJExtension:可以字典轉(zhuǎn)模型,而且可以不用與字典中屬性一一對(duì)應(yīng),runtime,遍歷模型中有多少個(gè)屬性,直接去字典中取出對(duì)應(yīng)value,給模型賦值
// 1??setValuesForKeysWithDictionary:方法底層實(shí)現(xiàn):遍歷字典中所有key,去模型中查找對(duì)應(yīng)的屬性,把值給模型屬性賦值,即調(diào)用下面方法:
/*
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// source
// 這行代碼才是真正給模型的屬性賦值
[s setValue:dict[@"source"] forKey:@"source"]; //底層實(shí)現(xiàn)是:
2?? [s setValue:dict[@"source"] forKey:@"source"];
1.首先會(huì)去模型中查找有沒有setSource方法,直接調(diào)用set方法 [s setSource:dict[@"source"]];
2.去模型中查找有沒有source屬性,source = dict[@"source"]
3.去米線中查找有沒有_source屬性,_source = dict[@"source"]
4.調(diào)用對(duì)象的 setValue:forUndefinedKey:直接報(bào)錯(cuò)
[s setValue:obj forKey:key];
}];
*/
return s;
}
// 3??用KVC下隧,不想讓系統(tǒng)報(bào)錯(cuò)奢人,重寫系統(tǒng)方法思想:
// 1.想給系統(tǒng)方法添加功能
// 2.不想要系統(tǒng)實(shí)現(xiàn)
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
@end
7、MJExtention的底層實(shí)現(xiàn)
#import "NSObject+Model.h"
#import <objc/message.h>
// class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 獲取屬性列表
@implementation NSObject (Model)
/**
字典轉(zhuǎn)模型
@param dict 傳入需要轉(zhuǎn)模型的字典
@return 賦值好的模型
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
//思路: runtime遍歷模型中屬性,去字典中取出對(duì)應(yīng)value,在給模型中屬性賦值
// 1.獲取模型中所有屬性 -> 保存到類
// ivar:下劃線成員變量 和 Property:屬性
// 獲取成員變量列表
// class:獲取哪個(gè)類成員變量列表
// count:成員變量總數(shù)
//這個(gè)方法得到一個(gè)裝有成員變量的數(shù)組
//class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
int count = 0;
// 成員變量數(shù)組 指向數(shù)組第0個(gè)元素
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for (int i = 0; i < count; i++) {
// 獲取成員變量 user
Ivar ivar = ivarList[i];
// 獲取成員變量名稱淆院,即將C語(yǔ)言的字符轉(zhuǎn)為OC字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 獲取成員變量類型何乎,用于獲取二級(jí)字典的模型名字
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 將type這樣的字符串@"@\"User\"" 轉(zhuǎn)成 @"User"
type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];
type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 成員變量名稱轉(zhuǎn)換key,即去掉成員變量前面的下劃線
NSString *key = [ivarName substringFromIndex:1];
// 從字典中取出對(duì)應(yīng)value dict[@"user"] -> 字典
id value = dict[key];
// 二級(jí)轉(zhuǎn)換
// 并且是自定義類型,才需要轉(zhuǎn)換
if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典才需要轉(zhuǎn)換
Class className = NSClassFromString(type);
// 字典轉(zhuǎn)模型
value = [className modelWithDict:value];
}
// 給模型中屬性賦值 key:user value:字典 -> 模型
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
@end