對(duì)于從事 iOS 開(kāi)發(fā)人員來(lái)說(shuō),所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能說(shuō)出【給分類動(dòng)態(tài)添加屬性 || 交換方法】,再問(wèn)一句【runtime 消息機(jī)制的調(diào)用流程 || 能體現(xiàn)runtime 強(qiáng)大之處的應(yīng)用場(chǎng)景】,到這,能知道答案的寥寥無(wú)幾,很少有人會(huì)說(shuō)到【黑魔法】這三個(gè)字
** runtime** 是 iOS 編程中比較難的模塊,想要深入學(xué)習(xí) OC,那? runtime 是你必須要熟練掌握的東西,下面是我對(duì)runtime的整理,從零開(kāi)始,由淺入深,并且?guī)Я藥讉€(gè)runtime實(shí)際開(kāi)發(fā)的應(yīng)用場(chǎng)景。
目錄:
runtime 概念
runtime 消息機(jī)制
runtime 方法調(diào)用流程「消息機(jī)制」
runtime 運(yùn)行時(shí)常見(jiàn)作用
runtime 常用開(kāi)發(fā)應(yīng)用場(chǎng)景「工作掌握」
1.runtime 交換方法
2.UITextField占位文字顏色(工具類)
3.runtime 給分類動(dòng)態(tài)添加屬性
4.runtime 字典轉(zhuǎn)模型(Runtime 考慮三種情況實(shí)現(xiàn))
runtime 運(yùn)行時(shí)其它作用「面試熟悉」
1.動(dòng)態(tài)添加方法
2.動(dòng)態(tài)變量控制
3.實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔
4.runtime 下Class的各項(xiàng)操作
5.runtime 幾個(gè)參數(shù)概念
什么是 method swizzling(俗稱黑魔法)
最后一道面試題的注解
runtime模塊簡(jiǎn)友文章推薦(??數(shù)量較多)
實(shí)戰(zhàn)應(yīng)用場(chǎng)景(持續(xù))后續(xù)更新Swift3.1-runtime面試、工作
OC & Swift runtime & runloop 面試最常問(wèn)到的題整理【建議看】
runtime 概念
Objective-C是基于 C 的,它為 C 添加了面向?qū)ο?/b>的特性。它將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)期做的事放到了 runtime 運(yùn)行時(shí)來(lái)處理,可以說(shuō)runtime是我們 Objective-C 幕后工作者。
runtime(簡(jiǎn)稱運(yùn)行時(shí)),是一套 純C(C和匯編寫(xiě)的) 的API。而 OC 就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制,其中最主要的是【消息機(jī)制】。
對(duì)于 C 語(yǔ)言,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)。
OC的函數(shù)調(diào)用成為消息發(fā)送,屬于動(dòng)態(tài)調(diào)用過(guò)程。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
事實(shí)證明:在編譯階段,OC 可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過(guò)就不會(huì)報(bào)錯(cuò),只有當(dāng)運(yùn)行的時(shí)候才會(huì)報(bào)錯(cuò),這是因?yàn)镺C是運(yùn)行時(shí)動(dòng)態(tài)調(diào)用的。而 C 語(yǔ)言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)。
runtime 消息機(jī)制
我們寫(xiě) OC 代碼,它在運(yùn)行的時(shí)候也是轉(zhuǎn)換成了runtime方式運(yùn)行的。任何方法調(diào)用本質(zhì):就是發(fā)送一個(gè)消息(用runtime發(fā)送消息,OC 底層實(shí)現(xiàn)通過(guò)runtime實(shí)現(xiàn))。
消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)。
每一個(gè) OC 的方法,底層必然有一個(gè)與之對(duì)應(yīng)的runtime方法。

OC-->runtime
簡(jiǎn)單示例:
驗(yàn)證:方法調(diào)用,是否真的是轉(zhuǎn)換為消息機(jī)制?
必須要導(dǎo)入頭文件#import
注解1:我們導(dǎo)入系統(tǒng)的頭文件,一般用尖括號(hào)。
注解2:OC 解決消息機(jī)制方法提示步驟【查找build setting-> 搜索msg->objc_msgSend(YES --> NO)】
注解3:最終生成消息機(jī)制,編譯器做的事情,最終代碼,需要把當(dāng)前代碼重新編譯,用xcode編譯器,【clang -rewrite-objc main.m查看最終生成代碼】,示例:cd main.m --> 輸入前面指令,就會(huì)生成 .opp文件(C++代碼)
注解4:這里一般不會(huì)直接導(dǎo)入

message.h
示例代碼:OC 方法-->runtime 方法
說(shuō)明: eat(無(wú)參) 和 run(有參) 是 Person模型類中的私有方法「可以幫我調(diào)用私有方法」;// Person *p = [Person alloc];// 底層的實(shí)際寫(xiě)法Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));// p = [p init];p = objc_msgSend(p, sel_registerName("init"));// 調(diào)用對(duì)象方法(本質(zhì):讓對(duì)象發(fā)送消息)//[p eat];// 本質(zhì):讓類對(duì)象發(fā)送消息objc_msgSend(p,@selector(eat));objc_msgSend([Personclass],@selector(run:),20);//--------------------------- <#我是分割線#> ------------------------------//// 也許下面這種好理解一點(diǎn)// id objc = [NSObject alloc];id objc = objc_msgSend([NSObjectclass],@selector(alloc));// objc = [objc init];objc = objc_msgSend(objc,@selector(init));
runtime 方法調(diào)用流程「消息機(jī)制」
面試:消息機(jī)制方法調(diào)用流程
怎么去調(diào)用eat方法,對(duì)象方法:(保存到類對(duì)象的方法列表) ,類方法:(保存到元類(Meta Class)中方法列表)。
1.OC 在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象對(duì)應(yīng)的類或其父類中查找方法。。
2.注冊(cè)方法編號(hào)(這里用方法編號(hào)的好處,可以快速查找)。
3.根據(jù)方法編號(hào)去查找對(duì)應(yīng)方法。
4.找到只是最終函數(shù)實(shí)現(xiàn)地址,根據(jù)地址去方法區(qū)調(diào)用對(duì)應(yīng)函數(shù)。
補(bǔ)充:一個(gè)objc對(duì)象的isa的指針指向什么?有什么作用?
每一個(gè)對(duì)象內(nèi)部都有一個(gè)isa指針,這個(gè)指針是指向它的真實(shí)類型,根據(jù)這個(gè)指針就能知道將來(lái)調(diào)用哪個(gè)類的方法。
runtime 常見(jiàn)作用
動(dòng)態(tài)交換兩個(gè)方法的實(shí)現(xiàn)
動(dòng)態(tài)添加屬性
實(shí)現(xiàn)字典轉(zhuǎn)模型的自動(dòng)轉(zhuǎn)換
發(fā)送消息
動(dòng)態(tài)添加方法
攔截并替換方法
實(shí)現(xiàn) NSCoding 的自動(dòng)歸檔和解檔
runtime 常用開(kāi)發(fā)應(yīng)用場(chǎng)景「工作掌握」
runtime 交換方法
應(yīng)用場(chǎng)景:當(dāng)?shù)谌娇蚣?或者 系統(tǒng)原生方法功能不能滿足我們的時(shí)候,我們可以在保持系統(tǒng)原有方法功能的基礎(chǔ)上,添加額外的功能。
需求:加載一張圖片直接用[UIImage imageNamed:@"image"];是無(wú)法知道到底有沒(méi)有加載成功。給系統(tǒng)的imageNamed添加額外功能(是否加載圖片成功)。
方案一:繼承系統(tǒng)的類,重寫(xiě)方法.(弊端:每次使用都需要導(dǎo)入)
方案二:使用 runtime,交換方法.
實(shí)現(xiàn)步驟:
1.給系統(tǒng)的方法添加分類
2.自己實(shí)現(xiàn)一個(gè)帶有擴(kuò)展功能的方法
3.交換方法,只需要交換一次,
案例代碼:方法+調(diào)用+打印輸出
- (void)viewDidLoad {? ? [superviewDidLoad];// 方案一:先搞個(gè)分類,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;// 方案二:交換 imageNamed 和 ln_imageNamed 的實(shí)現(xiàn),就能調(diào)用 imageNamed,間接調(diào)用 ln_imageNamed 的實(shí)現(xiàn)。UIImage*image = [UIImageimageNamed:@"123"];}#import@implementationUIImage(Image)/**
load方法: 把類加載進(jìn)內(nèi)存的時(shí)候調(diào)用,只會(huì)調(diào)用一次
方法應(yīng)先交換,再去調(diào)用
*/+ (void)load {// 1.獲取 imageNamed方法地址// class_getClassMethod(獲取某個(gè)類的方法)Method imageNamedMethod = class_getClassMethod(self,@selector(imageNamed:));// 2.獲取 ln_imageNamed方法地址Method ln_imageNamedMethod = class_getClassMethod(self,@selector(ln_imageNamed:));// 3.交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式;「method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)」method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);}/**
看清楚下面是不會(huì)有死循環(huán)的
調(diào)用 imageNamed => ln_imageNamed
調(diào)用 ln_imageNamed => imageNamed
*/// 加載圖片 且 帶判斷是否加載成功+ (UIImage*)ln_imageNamed:(NSString*)name {UIImage*image = [UIImageln_imageNamed:name];if(image) {NSLog(@"runtime添加額外功能--加載成功");? ? }else{NSLog(@"runtime添加額外功能--加載失敗");? ? }returnimage;}/**
不能在分類中重寫(xiě)系統(tǒng)方法imageNamed,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super
所以第二步,我們要 自己實(shí)現(xiàn)一個(gè)帶有擴(kuò)展功能的方法.
+ (UIImage *)imageNamed:(NSString *)name {
}
*/@end// 打印輸出2016-02-1717:52:14.693runtime[12761:543574] runtime添加額外功能--加載成功
總結(jié):我們所做的就是在方法調(diào)用流程第三步的時(shí)候,交換兩個(gè)方法地址指向。而且我們改變指向要在系統(tǒng)的imageNamed:方法調(diào)用前,所以將代碼寫(xiě)在了分類的load方法里。最后當(dāng)運(yùn)行的時(shí)候系統(tǒng)的方法就會(huì)去找我們的方法的實(shí)現(xiàn)。
runtime 給分類動(dòng)態(tài)添加屬性
原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。
應(yīng)用場(chǎng)景:給系統(tǒng)的類添加屬性的時(shí)候,可以使用runtime動(dòng)態(tài)添加屬性方法。
注解:系統(tǒng)NSObject添加一個(gè)分類,我們知道在分類中是不能夠添加成員屬性的,雖然我們用了@property,但是僅僅會(huì)自動(dòng)生成get和set方法的聲明,并沒(méi)有帶下劃線的屬性和方法實(shí)現(xiàn)生成。但是我們可以通過(guò)runtime就可以做到給它方法的實(shí)現(xiàn)。
需求:給系統(tǒng) NSObject 類動(dòng)態(tài)添加屬性name字符串。
案例代碼:方法+調(diào)用+打印
@interfaceNSObject(Property)// @property分類:只會(huì)生成get,set方法聲明,不會(huì)生成實(shí)現(xiàn),也不會(huì)生成下劃線成員屬性@propertyNSString*name;@propertyNSString*height;@end@implementationNSObject(Property)- (void)setName:(NSString*)name {// objc_setAssociatedObject(將某個(gè)值跟某個(gè)對(duì)象關(guān)聯(lián)起來(lái),將某個(gè)值存儲(chǔ)到某個(gè)對(duì)象中)// object:給哪個(gè)對(duì)象添加屬性// key:屬性名稱// value:屬性值// policy:保存策略objc_setAssociatedObject(self,@"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSString*)name {returnobjc_getAssociatedObject(self,@"name");}// 調(diào)用NSObject*objc = [[NSObjectalloc] init];objc.name =@"123";NSLog(@"runtime動(dòng)態(tài)添加屬性name==%@",objc.name);// 打印輸出2016-02-1719:37:10.530runtime[12761:543574] runtime動(dòng)態(tài)添加屬性--name ==123
總結(jié):其實(shí),給屬性賦值的本質(zhì),就是讓屬性與一個(gè)對(duì)象產(chǎn)生關(guān)聯(lián),所以要給NSObject的分類的name屬性賦值就是讓name和NSObject產(chǎn)生關(guān)聯(lián),而runtime可以做到這一點(diǎn)。
runtime 字典轉(zhuǎn)模型
字典轉(zhuǎn)模型的方式:
一個(gè)一個(gè)的給模型屬性賦值(初學(xué)者)。
字典轉(zhuǎn)模型KVC實(shí)現(xiàn)
KVC 字典轉(zhuǎn)模型弊端:必須保證,模型中的屬性和字典中的key一一對(duì)應(yīng)。
如果不一致,就會(huì)調(diào)用[ setValue:forUndefinedKey:]報(bào)key找不到的錯(cuò)。
分析:模型中的屬性和字典的key不一一對(duì)應(yīng),系統(tǒng)就會(huì)調(diào)用setValue:forUndefinedKey:報(bào)錯(cuò)。
解決:重寫(xiě)對(duì)象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋,就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了。
字典轉(zhuǎn)模型Runtime實(shí)現(xiàn)
思路:利用運(yùn)行時(shí),遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對(duì)應(yīng)的值,給模型的屬性賦值(從提醒:字典中取值,不一定要全部取出來(lái))。
考慮情況:
1.當(dāng)字典的key和模型的屬性匹配不上。
2.模型中嵌套模型(模型屬性是另外一個(gè)模型對(duì)象)。
3.數(shù)組中裝著模型(模型的屬性是一個(gè)數(shù)組,數(shù)組中是一個(gè)個(gè)模型對(duì)象)。
注解:根據(jù)上面的三種特殊情況,先是字典的key和模型的屬性不對(duì)應(yīng)的情況。不對(duì)應(yīng)有兩種,一種是字典的鍵值大于模型屬性數(shù)量,這時(shí)候我們不需要任何處理,因?yàn)閞untime是先遍歷模型所有屬性,再去字典中根據(jù)屬性名找對(duì)應(yīng)值進(jìn)行賦值,多余的鍵值對(duì)也當(dāng)然不會(huì)去看了;另外一種是模型屬性數(shù)量大于字典的鍵值對(duì),這時(shí)候由于屬性沒(méi)有對(duì)應(yīng)值會(huì)被賦值為nil,就會(huì)導(dǎo)致crash,我們只需加一個(gè)判斷即可。考慮三種情況下面一一注解;
步驟:提供一個(gè)NSObject分類,專門字典轉(zhuǎn)模型,以后所有模型都可以通過(guò)這個(gè)分類實(shí)現(xiàn)字典轉(zhuǎn)模型。
MJExtension字典轉(zhuǎn)模型實(shí)現(xiàn)
底層也是對(duì)runtime的封裝,才可以把一個(gè)模型中所有屬性遍歷出來(lái)。(你之所以看不懂,是MJ封裝了很多層而已^_^.)。
這里針對(duì)字典轉(zhuǎn)模型 KVC 實(shí)現(xiàn),就不做詳解了,如果你 對(duì) KVC 詳解使用或是實(shí)現(xiàn)原理 不是很清楚的,可以參考實(shí)用「KVC編碼 & KVO監(jiān)聽(tīng)
字典轉(zhuǎn)模型 Runtime 方式實(shí)現(xiàn):
說(shuō)明:下面這個(gè)示例,是考慮三種情況包含在內(nèi)的轉(zhuǎn)換示例,具體可以看圖上的注解

Runtime 字典轉(zhuǎn)模型
1、runtime 字典轉(zhuǎn)模型-->字典的key和模型的屬性不匹配「模型屬性數(shù)量大于字典鍵值對(duì)數(shù)」,這種情況處理如下:
// Runtime:根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value給模型屬性賦值// 思路:遍歷模型中所有屬性->使用運(yùn)行時(shí)+ (instancetype)modelWithDict:(NSDictionary*)dict{// 1.創(chuàng)建對(duì)應(yīng)的對(duì)象idobjc = [[selfalloc] init];// 2.利用runtime給對(duì)象中的屬性賦值/**
class_copyIvarList: 獲取類中的所有成員變量
Ivar:成員變量
第一個(gè)參數(shù):表示獲取哪個(gè)類中的成員變量
第二個(gè)參數(shù):表示這個(gè)類有多少成員變量,傳入一個(gè)Int變量地址,會(huì)自動(dòng)給這個(gè)變量賦值
返回值Ivar *:指的是一個(gè)ivar數(shù)組,會(huì)把所有成員屬性放在一個(gè)數(shù)組中,通過(guò)返回的數(shù)組就能全部獲取到。
count: 成員變量個(gè)數(shù)
*/unsignedintcount =0;// 獲取類中的所有成員變量Ivar *ivarList = class_copyIvarList(self, &count);// 遍歷所有成員變量for(inti =0; i < count; i++) {// 根據(jù)角標(biāo),從數(shù)組取出對(duì)應(yīng)的成員變量Ivar ivar = ivarList[i];// 獲取成員變量名字NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];// 處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取)NSString*key = [ivarName substringFromIndex:1];// 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的valueidvalue = dict[key];// 【如果模型屬性數(shù)量大于字典鍵值對(duì)數(shù)理,模型屬性會(huì)被賦值為nil】// 而報(bào)錯(cuò) (could not set nil as the value for the key age.)if(value) {// 給模型中屬性賦值[objc setValue:value forKey:key];? ? ? ? }? ? }returnobjc;}
注解:這里在獲取模型類中的所有屬性名,是采取class_copyIvarList先獲取成員變量(以下劃線開(kāi)頭) ,然后再處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取) 得到屬性名。
原因:Ivar:成員變量,以下劃線開(kāi)頭,Property 屬性
獲取類里面屬性class_copyPropertyList
獲取類中的所有成員變量class_copyIvarList
{int_a;// 成員變量}@property(nonatomic,assign)NSIntegerattitudes_count;// 屬性這里有成員變量,就不會(huì)漏掉屬性;如果有屬性,可能會(huì)漏掉成員變量;
使用runtime字典轉(zhuǎn)模型獲取模型屬性名的時(shí)候,最好獲取成員屬性名Ivar因?yàn)榭赡軙?huì)有個(gè)屬性是沒(méi)有setter和getter方法的。
2、runtime 字典轉(zhuǎn)模型-->模型中嵌套模型「模型屬性是另外一個(gè)模型對(duì)象」,這種情況處理如下:
+ (instancetype)modelWithDict2:(NSDictionary*)dict{// 1.創(chuàng)建對(duì)應(yīng)的對(duì)象idobjc = [[selfalloc] init];// 2.利用runtime給對(duì)象中的屬性賦值unsignedintcount =0;// 獲取類中的所有成員變量Ivar *ivarList = class_copyIvarList(self, &count);// 遍歷所有成員變量for(inti =0; i < count; i++) {// 根據(jù)角標(biāo),從數(shù)組取出對(duì)應(yīng)的成員變量Ivar ivar = ivarList[i];// 獲取成員變量名字NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];// 獲取成員變量類型NSString*ivarType = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];// 替換: @\"User\" -> UserivarType = [ivarType stringByReplacingOccurrencesOfString:@"\""withString:@""];? ? ? ? ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@"withString:@""];// 處理成員屬性名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取)NSString*key = [ivarName substringFromIndex:1];// 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的valueidvalue = dict[key];//--------------------------- <#我是分割線#> ------------------------------////// 二級(jí)轉(zhuǎn)換:如果字典中還有字典,也需要把對(duì)應(yīng)的字典轉(zhuǎn)換成模型// 判斷下value是否是字典,并且是自定義對(duì)象才需要轉(zhuǎn)換if([value isKindOfClass:[NSDictionaryclass]] && ![ivarType hasPrefix:@"NS"]) {// 字典轉(zhuǎn)換成模型 userDict => User模型, 轉(zhuǎn)換成哪個(gè)模型// 根據(jù)字符串類名生成類對(duì)象Class modelClass =NSClassFromString(ivarType);if(modelClass) {// 有對(duì)應(yīng)的模型才需要轉(zhuǎn)// 把字典轉(zhuǎn)模型value = [modelClass modelWithDict2:value];? ? ? ? ? ? }? ? ? ? }// 給模型中屬性賦值if(value) {? ? ? ? ? ? [objc setValue:value forKey:key];? ? ? ? }? ? }returnobjc;}
3、runtime 字典轉(zhuǎn)模型-->數(shù)組中裝著模型「模型的屬性是一個(gè)數(shù)組,數(shù)組中是字典模型對(duì)象」,這種情況處理如下:
// Runtime:根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value給模型屬性賦值// 思路:遍歷模型中所有屬性->使用運(yùn)行時(shí)+ (instancetype)modelWithDict3:(NSDictionary*)dict{// 1.創(chuàng)建對(duì)應(yīng)的對(duì)象idobjc = [[selfalloc] init];// 2.利用runtime給對(duì)象中的屬性賦值unsignedintcount =0;// 獲取類中的所有成員變量Ivar *ivarList = class_copyIvarList(self, &count);// 遍歷所有成員變量for(inti =0; i < count; i++) {// 根據(jù)角標(biāo),從數(shù)組取出對(duì)應(yīng)的成員變量Ivar ivar = ivarList[i];// 獲取成員變量名字NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];// 處理成員屬性名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取)NSString*key = [ivarName substringFromIndex:1];// 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的valueidvalue = dict[key];//--------------------------- <#我是分割線#> ------------------------------////// 三級(jí)轉(zhuǎn)換:NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型.// 判斷值是否是數(shù)組if([value isKindOfClass:[NSArrayclass]]) {// 判斷對(duì)應(yīng)類有沒(méi)有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議// arrayContainModelClass 提供一個(gè)協(xié)議,只要遵守這個(gè)協(xié)議的類,都能把數(shù)組中的字典轉(zhuǎn)模型if([selfrespondsToSelector:@selector(arrayContainModelClass)]) {// 轉(zhuǎn)換成id類型,就能調(diào)用任何對(duì)象的方法ididSelf =self;// 獲取數(shù)組中字典對(duì)應(yīng)的模型NSString*type =? [idSelf arrayContainModelClass][key];// 生成模型Class classModel =NSClassFromString(type);NSMutableArray*arrM = [NSMutableArrayarray];// 遍歷字典數(shù)組,生成模型數(shù)組for(NSDictionary*dictinvalue) {// 字典轉(zhuǎn)模型idmodel =? [classModel modelWithDict3:dict];? ? ? ? ? ? ? ? ? ? [arrM addObject:model];? ? ? ? ? ? ? ? }// 把模型數(shù)組賦值給valuevalue = arrM;? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? }// 如果模型屬性數(shù)量大于字典鍵值對(duì)數(shù)理,模型屬性會(huì)被賦值為nil,而報(bào)錯(cuò)if(value) {// 給模型中屬性賦值[objc setValue:value forKey:key];? ? ? ? }? ? }returnobjc;}

runtime字典轉(zhuǎn)模型-->數(shù)組中裝著模型 打印輸出
總結(jié):我們既然能獲取到屬性類型,那就可以攔截到模型的那個(gè)數(shù)組屬性,進(jìn)而對(duì)數(shù)組中每個(gè)模型遍歷并字典轉(zhuǎn)模型,但是我們不知道數(shù)組中的模型都是什么類型,我們可以聲明一個(gè)方法,該方法目的不是讓其調(diào)用,而是讓其實(shí)現(xiàn)并返回模型的類型。
這里提到的你如果不是很清楚,建議參考我的Demo,重要的部分代碼中都有相應(yīng)的注解和文字打印,運(yùn)行程序可以很直觀的表現(xiàn)。
runtime 其它作用「面試熟悉」
動(dòng)態(tài)添加方法
應(yīng)用場(chǎng)景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類,添加方法解決。
注解:OC 中我們很習(xí)慣的會(huì)用懶加載,當(dāng)用到的時(shí)候才去加載它,但是實(shí)際上只要一個(gè)類實(shí)現(xiàn)了某個(gè)方法,就會(huì)被加載進(jìn)內(nèi)存。當(dāng)我們不想加載這么多方法的時(shí)候,就會(huì)使用到runtime動(dòng)態(tài)的添加方法。
需求:runtime 動(dòng)態(tài)添加方法處理調(diào)用一個(gè)未實(shí)現(xiàn)的方法 和 去除報(bào)錯(cuò)。
案例代碼:方法+調(diào)用+打印輸出
- (void)viewDidLoad {? ? [superviewDidLoad];? ? ? Person *p = [[Person alloc] init];// 默認(rèn)person,沒(méi)有實(shí)現(xiàn)run:方法,可以通過(guò)performSelector調(diào)用,但是會(huì)報(bào)錯(cuò)。// 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)[p performSelector:@selector(run:) withObject:@10];}@implementationPerson// 沒(méi)有返回值,1個(gè)參數(shù)// void,(id,SEL)voidaaa(idself, SEL _cmd,NSNumber*meter) {NSLog(@"跑了%@米", meter);}// 任何方法默認(rèn)都有兩個(gè)隱式參數(shù),self,_cmd(當(dāng)前方法的方法編號(hào))// 什么時(shí)候調(diào)用:只要一個(gè)對(duì)象調(diào)用了一個(gè)未實(shí)現(xiàn)的方法就會(huì)調(diào)用這個(gè)方法,進(jìn)行處理// 作用:動(dòng)態(tài)添加方法,處理未實(shí)現(xiàn)+ (BOOL)resolveInstanceMethod:(SEL)sel{// [NSStringFromSelector(sel) isEqualToString:@"run"];if(sel ==NSSelectorFromString(@"run:")) {// 動(dòng)態(tài)添加run方法// class: 給哪個(gè)類添加方法// SEL: 添加哪個(gè)方法,即添加方法的方法編號(hào)// IMP: 方法實(shí)現(xiàn) => 函數(shù) => 函數(shù)入口 => 函數(shù)名(添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址))// type: 方法類型,(返回值+參數(shù)類型) v:void @:對(duì)象->self :表示SEL->_cmdclass_addMethod(self, sel, (IMP)aaa,"v@:@");returnYES;? ? }return[superresolveInstanceMethod:sel];}@end// 打印輸出2016-02-1719:05:03.917runtime[12761:543574] runtime動(dòng)態(tài)添加方法--跑了10米
動(dòng)態(tài)變量控制
現(xiàn)在有一個(gè)Person類,創(chuàng)建 xiaoming對(duì)象
動(dòng)態(tài)獲取 XiaoMing 類中的所有屬性 [當(dāng)然包括私有]
Ivar *ivar = class_copyIvarList([self.xiaomingclass], &count);
遍歷屬性找到對(duì)應(yīng)name字段
constchar*varName = ivar_getName(var);
修改對(duì)應(yīng)的字段值成20
object_setIvar(self.xiaoMing,var, @"20");
代碼參考
-(void)answer{unsignedintcount =0;? ? Ivar *ivar = class_copyIvarList([self.xiaoMingclass], &count);for(inti =0; i
實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔
如果你實(shí)現(xiàn)過(guò)自定義模型數(shù)據(jù)持久化的過(guò)程,那么你也肯定明白,如果一個(gè)模型有許多個(gè)屬性,那么我們需要對(duì)每個(gè)屬性都實(shí)現(xiàn)一遍encodeObject和decodeObjectForKey方法,如果這樣的模型又有很多個(gè),這還真的是一個(gè)十分麻煩的事情。下面來(lái)看看簡(jiǎn)單的實(shí)現(xiàn)方式。
假設(shè)現(xiàn)在有一個(gè)Movie類,有3個(gè)屬性。先看下.h文件
// Movie.h文件//1. 如果想要當(dāng)前類可以實(shí)現(xiàn)歸檔與反歸檔,需要遵守一個(gè)協(xié)議NSCoding@interfaceMovie: NSObject@property(nonatomic, copy) NSString *movieId;@property(nonatomic, copy) NSString *movieName;@property(nonatomic, copy) NSString *pic_url;@end
如果是正常寫(xiě)法,.m文件應(yīng)該是這樣的:
// Movie.m文件@implementationMovie- (void)encodeWithCoder:(NSCoder *)aCoder{? ? [aCoderencodeObject:_movieIdforKey:@"id"];? ? [aCoderencodeObject:_movieNameforKey:@"name"];? ? [aCoderencodeObject:_pic_urlforKey:@"url"];}- (id)initWithCoder:(NSCoder *)aDecoder{if(self = [superinit]) {? ? ? ? self.movieId = [aDecoderdecodeObjectForKey:@"id"];? ? ? ? self.movieName = [aDecoderdecodeObjectForKey:@"name"];? ? ? ? self.pic_url = [aDecoderdecodeObjectForKey:@"url"];? ? }returnself;}@end
如果這里有100個(gè)屬性,那么我們也只能把100個(gè)屬性都給寫(xiě)一遍嗎。
不過(guò)你會(huì)使用runtime后,這里就有更簡(jiǎn)便的方法,如下。
#import"Movie.h"#import@implementationMovie- (void)encodeWithCoder:(NSCoder*)encoder{unsignedintcount =0;? ? Ivar *ivars = class_copyIvarList([Movieclass], &count);for(inti =0; i
這樣的方式實(shí)現(xiàn),不管有多少個(gè)屬性,寫(xiě)這幾行代碼就搞定了。怎么,代碼有點(diǎn)多,
好說(shuō)下面看看更加簡(jiǎn)便的方法:兩句代碼搞定。
#import"Movie.h"#import#define encodeRuntime(A) \\unsignedintcount =0;\Ivar *ivars = class_copyIvarList([Aclass], &count);\for(inti =0; i
優(yōu)化:上面是encodeWithCoder和initWithCoder這兩個(gè)方法抽成宏。我們可以把這兩個(gè)宏單獨(dú)放到一個(gè)文件里面,這里以后需要進(jìn)行數(shù)據(jù)持久化的模型都可以直接使用這兩個(gè)宏。
runtime 下Class的各項(xiàng)操作
下面是 runtime 下Class的常見(jiàn)方法 及 帶有使用示例代碼。各項(xiàng)操作,原著 http://www.itdecent.cn/p/46dd81402f63
unsigned int count;
獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([selfclass], &count);for(unsignedinti=0; i%@", [NSStringstringWithUTF8String:propertyName]); }
獲取方法列表
Method*methodList=class_copyMethodList([selfclass], &count);for(unsigned int i; i
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
獲取成員變量列表
Ivar *ivarList = class_copyIvarList([selfclass], &count);for(unsignedinti; i%@", [NSStringstringWithUTF8String:ivarName]);? }
獲取協(xié)議列表
__unsafe_unretainedProtocol **protocolList = class_copyProtocolList([selfclass], &count);for(unsignedinti; i%@", [NSStringstringWithUTF8String:protocolName]);? }
現(xiàn)在有一個(gè)Person類,和person創(chuàng)建的xiaoming對(duì)象,有test1和test2兩個(gè)方法
獲得類方法
ClassPersonClass = object_getClass([Personclass]);SEL oriSEL = @selector(test1);MethodoriMethod= _class_getMethod(xiaomingClass, oriSEL);
獲得實(shí)例方法
ClassPersonClass = object_getClass([xiaomingclass]);SEL oriSEL = @selector(test2);MethodcusMethod=class_getInstanceMethod(xiaomingClass, oriSEL);
添加方法
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
替換原方法實(shí)現(xiàn)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(oriMethod, cusMethod);
常用方法
// 得到類的所有方法Method *allMethods = class_copyMethodList([Personclass],&count);// 得到所有成員變量Ivar *allVariables = class_copyIvarList([Personclass],&count);// 得到所有屬性objc_property_t *properties = class_copyPropertyList([Personclass],&count);// 根據(jù)名字得到類變量的Ivar指針,但是這個(gè)在OC中好像毫無(wú)意義Ivar oneCVIvar = class_getClassVariable([Personclass],name);// 根據(jù)名字得到實(shí)例變量的Ivar指針I(yè)var oneIVIvar = class_getInstanceVariable([Personclass],name);// 找到后可以直接對(duì)私有變量賦值object_setIvar(_per, oneIVIvar, @"Mike");//強(qiáng)制修改name屬性/* 動(dòng)態(tài)添加方法:
第一個(gè)參數(shù)表示Class cls 類型;
第二個(gè)參數(shù)表示待調(diào)用的方法名稱;
第三個(gè)參數(shù)(IMP)myAddingFunction,IMP是一個(gè)函數(shù)指針,這里表示指定具體實(shí)現(xiàn)方法myAddingFunction;
第四個(gè)參數(shù)表方法的參數(shù),0代表沒(méi)有參數(shù);
*/class_addMethod([_perclass],@selector(sayHi), (IMP)myAddingFunction,0);// 交換兩個(gè)方法method_exchangeImplementations(method1, method2);// 關(guān)聯(lián)兩個(gè)對(duì)象objc_setAssociatedObject(idobject, const void *key, id value, objc_AssociationPolicy policy)/*
id object? ? ? ? ? ? ? ? ? ? :表示關(guān)聯(lián)者,是一個(gè)對(duì)象,變量名理所當(dāng)然也是object
const void *key? ? ? ? ? ? ? :獲取被關(guān)聯(lián)者的索引key
id value? ? ? ? ? ? ? ? ? ? ? :被關(guān)聯(lián)者,這里是一個(gè)block
objc_AssociationPolicy policy : 關(guān)聯(lián)時(shí)采用的協(xié)議,有assign,retain,copy等協(xié)議,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
*/
runtime 幾個(gè)參數(shù)概念
以上的幾種方法應(yīng)該算是runtime在實(shí)際場(chǎng)景中所應(yīng)用的大部分的情況了,平常的編碼中差不多足夠用了。
這里在對(duì)runtime幾個(gè)參數(shù)概念,做一簡(jiǎn)單說(shuō)明
1、objc_msgSend
這是個(gè)最基本的用于發(fā)送消息的函數(shù)。
其實(shí)編譯器會(huì)根據(jù)情況在objc_msgSend,objc_msgSend_stret,,objc_msgSendSuper, 或objc_msgSendSuper_stret四個(gè)方法中選擇一個(gè)來(lái)調(diào)用。如果消息是傳遞給超類,那么會(huì)調(diào)用名字帶有Super的函數(shù);如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡(jiǎn)單值時(shí),那么會(huì)調(diào)用名字帶有stret的函數(shù)。
2、SEL
objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的ID,而這個(gè)ID的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;
其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()``或者 Runtime系統(tǒng)的sel_registerName函數(shù)來(lái)獲得一個(gè)SEL類型的方法選擇器。
3、id
objc_msgSend第一個(gè)參數(shù)類型為id,大家對(duì)它都不陌生,它是一個(gè)指向類實(shí)例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結(jié)構(gòu)體包含一個(gè)isa指針,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類。
4、runtime.h里Class的定義
structobjc_class{Class isa? OBJC_ISA_AVAILABILITY;//每個(gè)Class都有一個(gè)isa指針#if!__OBJC2__Class super_class? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;//父類constchar*name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;//類名longversion? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;//類版本longinfo? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;//!*!供運(yùn)行期使用的一些位標(biāo)識(shí)。如:CLS_CLASS (0x1L)表示該類為普通class; CLS_META(0x2L)表示該類為metaclass等(runtime.h中有詳細(xì)列出)longinstance_size? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;//實(shí)例大小structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;//存儲(chǔ)每個(gè)實(shí)例變量的內(nèi)存地址structobjc_method_list**methodListsOBJC2_UNAVAILABLE;//!*!根據(jù)info的信息確定是類還是實(shí)例,運(yùn)行什么函數(shù)方法等structobjc_cache*cacheOBJC2_UNAVAILABLE;//緩存structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;//協(xié)議#endif} OBJC2_UNAVAILABLE;
可以看到運(yùn)行時(shí)一個(gè)類還關(guān)聯(lián)了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協(xié)議。
在objc_class結(jié)構(gòu)體中:``ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說(shuō)可以動(dòng)態(tài)修改*methodLists的值來(lái)添加成員方法,這也是Category實(shí)現(xiàn)的原理。
上面講到的所有東西都在Demo里,如果你感覺(jué)這樣難以理解,那強(qiáng)烈建議你下載Demo ,運(yùn)行代碼加上文字注解,效果會(huì)更好,如果你覺(jué)得不錯(cuò),還請(qǐng)為我的Demo star一個(gè)。
什么是 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。

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。

交換方法
這里可以參考簡(jiǎn)友這篇:【Runtime Method Swizzling開(kāi)發(fā)實(shí)例匯總】http://www.itdecent.cn/p/f6dad8e1b848
這里可以參考權(quán)威這篇:OC運(yùn)行時(shí)黑魔法 Method Swizzling

? -- 小黑不要走,我一定咬吃了你!
最后一道面試題的注解
下面的代碼輸出什么?
@implementationSon:NSObject- (id)init{self= [superinit];if(self) {NSLog(@"%@",NSStringFromClass([selfclass]));NSLog(@"%@",NSStringFromClass([superclass]));? ? }returnself;}@end
先思考一下,會(huì)打印出來(lái)什么?
關(guān)注我的更多干貨分享 ^_^.
答案:都輸出 Son
class獲取當(dāng)前方法的調(diào)用者的類,superClass獲取當(dāng)前方法的調(diào)用者的父類,super僅僅是一個(gè)編譯指示器,就是給編譯器看的,不是一個(gè)指針。
本質(zhì):只要編譯器看到super這個(gè)標(biāo)志,就會(huì)讓當(dāng)前對(duì)象去調(diào)用父類方法,本質(zhì)還是當(dāng)前對(duì)象在調(diào)用
這個(gè)題目主要是考察關(guān)于objc中對(duì)self和super的理解:
self是類的隱藏參數(shù),指向當(dāng)前調(diào)用方法的這個(gè)類的實(shí)例。而super本質(zhì)是一個(gè)編譯器標(biāo)示符,和self是指向的同一個(gè)消息接受者
當(dāng)使用self調(diào)用方法時(shí),會(huì)從當(dāng)前類的方法列表中開(kāi)始找,如果沒(méi)有,就從父類中再找;
而當(dāng)使用super時(shí),則從父類的方法列表中開(kāi)始找。然后調(diào)用父類的這個(gè)方法
調(diào)用[self class]時(shí),會(huì)轉(zhuǎn)化成objc_msgSend函數(shù)
idobjc_msgSend(idself, SEL op, ...)- 調(diào)用 `[superclass]`時(shí),會(huì)轉(zhuǎn)化成 `objc_msgSendSuper` 函數(shù).idobjc_msgSendSuper(structobjc_super *super, SEL op, ...)第一個(gè)參數(shù)是 objc_super 這樣一個(gè)結(jié)構(gòu)體,其定義如下structobjc_super { __unsafe_unretainedidreceiver; __unsafe_unretainedClass super_class; };第一個(gè)成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個(gè)參數(shù)self第二個(gè)成員是記錄當(dāng)前類的父類是什么,告訴程序從父類中開(kāi)始找方法,找到方法后,最后內(nèi)部是使用 objc_msgSend(objc_super->receiver,@selector(class))去調(diào)用, 此時(shí)已經(jīng)和[selfclass]調(diào)用相同了,故上述輸出結(jié)果仍然返回 Sonobjc Runtime 開(kāi)源代碼對(duì)- (Class)class方法的實(shí)現(xiàn)-(Class)class{returnobject_getClass(self); }
runtime模塊簡(jiǎn)友文章推薦(??數(shù)量較多)
簡(jiǎn)友runtime模塊推薦閱讀文章
西木完整總結(jié)http://www.itdecent.cn/p/6b905584f536
天口三水羊objc_msgSendhttp://www.itdecent.cn/p/9e1bc8d890f9
夜千尋墨詳解http://www.itdecent.cn/p/46dd81402f63
袁崢Seemygo快速上手http://www.itdecent.cn/p/e071206103a4
鄭欽洪_(kāi)實(shí)現(xiàn)自動(dòng)化歸檔http://www.itdecent.cn/p/bd24c3f3cd0a
HenryCheng消息機(jī)制http://www.itdecent.cn/p/f6300eb3ec3d
賣報(bào)的小畫(huà)家SureMethod Swizzling開(kāi)發(fā)實(shí)例匯總http://www.itdecent.cn/p/f6dad8e1b848
滕大鳥(niǎo)OC最實(shí)用的runtime總結(jié)http://www.itdecent.cn/p/ab966e8a82e2
黑花白花Runtime在實(shí)際開(kāi)發(fā)中的應(yīng)用http://www.itdecent.cn/p/851b21870d91
OC & Swift Runtime 面試最常問(wèn)到的題整理【建議看】
說(shuō)明:此面試題針對(duì)性的摘錄整理,只為方便 在面試路上準(zhǔn)備的你 ,會(huì)注有原文。
1、整理原文:2017年5月iOS招人心得(附面試題)
Runtime
objc在向一個(gè)對(duì)象發(fā)送消息時(shí),發(fā)生了什么?
什么時(shí)候會(huì)報(bào)unrecognized selector錯(cuò)誤?iOS有哪些機(jī)制來(lái)避免走到這一步?
能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么?
runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
給類添加一個(gè)屬性后,在類結(jié)構(gòu)體里哪些元素會(huì)發(fā)生變化?
RunLoop
runloop是來(lái)做什么的?runloop和線程有什么關(guān)系?主線程默認(rèn)開(kāi)啟了runloop么?子線程呢?
runloop的mode是用來(lái)做什么的?有幾種mode?
為什么把NSTimer對(duì)象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到4. 主運(yùn)行循環(huán)以后,滑動(dòng)scrollview的時(shí)候NSTimer卻不動(dòng)了?
蘋(píng)果是如何實(shí)現(xiàn)Autorelease Pool的?
//-------------------- 【我是分割線】 ---------------------//
整理原文:2017年iOS面試題總結(jié),附上答案
Runtime
01
問(wèn)題:objc在向一個(gè)對(duì)象發(fā)送消息時(shí),發(fā)生了什么?
解答:根據(jù)對(duì)象的 isa 指針找到類對(duì)象 id,在查詢類對(duì)象里面的 methodLists 方法函數(shù)列表,如果沒(méi)有在好到,在沿著 superClass ,尋找父類,再在父類 methodLists 方法列表里面查詢,最終找到 SEL ,根據(jù) id 和 SEL 確認(rèn) IMP(指針函數(shù)),在發(fā)送消息;
03
問(wèn)題:什么時(shí)候會(huì)報(bào)unrecognized selector錯(cuò)誤?iOS有哪些機(jī)制來(lái)避免走到這一步?
解答:當(dāng)發(fā)送消息的時(shí)候,我們會(huì)根據(jù)類里面的 methodLists 列表去查詢我們要?jiǎng)佑玫腟EL,當(dāng)查詢不到的時(shí)候,我們會(huì)一直沿著父類查詢,當(dāng)最終查詢不到的時(shí)候我們會(huì)報(bào)unrecognized selector錯(cuò)誤,當(dāng)系統(tǒng)查詢不到方法的時(shí)候,會(huì)調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel動(dòng)態(tài)解釋的方法來(lái)給我一次機(jī)會(huì)來(lái)添加,調(diào)用不到的方法?;蛘呶覀兛梢栽俅问褂?(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法來(lái)告訴系統(tǒng),該調(diào)用什么方法,一來(lái)保證不會(huì)崩潰。
04
問(wèn)題:能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么?
解答:1、不能向編譯后得到的類增加實(shí)例變量 2、能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?!窘忉尅浚?. 編譯后的類已經(jīng)注冊(cè)在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list? 實(shí)例變量的鏈表和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定,runtime會(huì)調(diào)用? class_setvarlayout 或 class_setWeaklvarLayout 來(lái)處理strong weak 引用.所以不能向存在的類中添加實(shí)例變量。2. 運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
05
問(wèn)題:runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
解答:runtime 對(duì)注冊(cè)的類, 會(huì)進(jìn)行布局,對(duì)于 weak 對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì) dealloc,假如 weak 指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵, 在這個(gè) weak 表中搜索,找到所有以a為鍵的 weak 對(duì)象,從而設(shè)置為 nil。
06
問(wèn)題:給類添加一個(gè)屬性后,在類結(jié)構(gòu)體里哪些元素會(huì)發(fā)生變化?
解答:instance_size :實(shí)例的內(nèi)存大??;objc_ivar_list *ivars:屬性列表
RunLoop
01
問(wèn)題:runloop是來(lái)做什么的?runloop和線程有什么關(guān)系?主線程默認(rèn)開(kāi)啟了runloop么?子線程呢?
解答:runloop: 字面意思就是跑圈,其實(shí)也就是一個(gè)循環(huán)跑圈,用來(lái)處理線程里面的事件和消息。runloop和線程的關(guān)系:每個(gè)線程如果想繼續(xù)運(yùn)行,不被釋放,就必須有一個(gè)runloop來(lái)不停的跑圈,以來(lái)處理線程里面的各個(gè)事件和消息。主線程默認(rèn)是開(kāi)啟一個(gè)runloop。也就是這個(gè)runloop才能保證我們程序正常的運(yùn)行。子線程是默認(rèn)沒(méi)有開(kāi)始runloop的
02
問(wèn)題:runloop的mode是用來(lái)做什么的?有幾種mode?
解答:model:是runloop里面的模式,不同的模式下的runloop處理的事件和消息有一定的差別。系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:(1)kCFRunLoopDefaultMode: App的默認(rèn) Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的。(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響。(3)UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用。(4)GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到。(5)kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode,沒(méi)有實(shí)際作用。注意iOS 對(duì)以上5中model進(jìn)行了封裝 NSDefaultRunLoopMode、NSRunLoopCommonModes
03
問(wèn)題:為什么把NSTimer對(duì)象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)以后,滑動(dòng)scrollview的時(shí)候NSTimer卻不動(dòng)了?
解答:nstime對(duì)象是在 NSDefaultRunLoopMode下面調(diào)用消息的,但是當(dāng)我們滑動(dòng)scrollview的時(shí)候,NSDefaultRunLoopMode模式就自動(dòng)切換到UITrackingRunLoopMode模式下面,卻不可以繼續(xù)響應(yīng)nstime發(fā)送的消息。所以如果想在滑動(dòng)scrollview的情況下面還調(diào)用nstime的消息,我們可以把nsrunloop的模式更改為NSRunLoopCommonModes.
04
問(wèn)題:蘋(píng)果是如何實(shí)現(xiàn)Autorelease Pool的?
解答:Autorelease Pool作用:緩存池,可以避免我們經(jīng)常寫(xiě)relase的一種方式。其實(shí)就是延遲release,將創(chuàng)建的對(duì)象,添加到最近的autoreleasePool中,等到autoreleasePool作用域結(jié)束的時(shí)候,會(huì)將里面所有的對(duì)象的引用計(jì)數(shù)器 - autorelease.
后續(xù)遇到針對(duì)runtime常面相關(guān),會(huì)及時(shí)在這里補(bǔ)充;
效果圖

來(lái)杯白開(kāi)水,寫(xiě)的小樣在下面 ~
附上寫(xiě)的小樣 Demo,重要的部分代碼中都有相應(yīng)的注解和文字打印,運(yùn)行程序可以很直觀的表現(xiàn)
【@碼員真愛(ài) : 真的不點(diǎn)進(jìn)去閱讀一下 ?】