引導
對于從事 iOS 開發(fā)人員來說,所有的人都會答出「 Runtime 是運行時 」蝇裤,什么情況下用 Runtime ?而姐,大部分人能說出「 給分類動態(tài)添加屬性 || 交換方法 」,再問一句「 Runtime 消息機制的調(diào)用流程 || 能體現(xiàn) Runtime 強大之處的應用場景 」涨共,到這岖寞,能知道答案的寥寥無幾抡四,很少有人會說到 “黑魔法” 這三個字。
Runtime 是 iOS 編程中比較難的模塊仗谆,想要深入學習 OC指巡,那 Runtime 是你必須要熟練掌握的東西,下面是我對 Runtime 的整理隶垮,從零開始藻雪,由淺入深,并且?guī)Я藥讉€ Runtime 實際開發(fā)應用場景 --> 大神可選擇性路過「思想」狸吞。
在「時間 & 知識 」有限內(nèi)勉耀,總結的文章難免有「未全指煎、不足 」的地方,還望各位好友指出便斥,可留言指正或是補充至壤,以提高文章質(zhì)量@白開水ln原著;
目錄:
runtime 概念
runtime 消息機制
runtime 方法調(diào)用流程「消息機制」
runtime 運行時常見作用
runtime 常用開發(fā)應用場景「工作掌握」
1.runtime 交換方法
2.UITextField占位文字顏色(工具類)
3.runtime 給分類動態(tài)添加屬性
4.runtime 字典轉模型(Runtime 考慮三種情況實現(xiàn))
runtime 運行時其它作用「面試熟悉」
1.動態(tài)添加方法
2.動態(tài)變量控制
3.實現(xiàn)NSCoding的自動歸檔和解檔
4.runtime 下Class的各項操作
5.runtime 幾個參數(shù)概念
什么是 method swizzling(俗稱黑魔法)
最后一道面試題的注解
runtime 模塊博文推薦(??數(shù)量較多)
runtime & runloop 面試最常問到的題整理【建議看】
Demo 重要的部分代碼中都有相應的注解和文字打印枢纠,運行程序可以很直觀的表現(xiàn)像街。
SourceCode、ToolsClass晋渺、WechatPublic-Codeidea
runtime 概念
Objective-C是基于 C 的,它為 C 添加了面向對象的特性些举。它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了 runtime 運行時來處理跟狱,可以說runtime是我們 Objective-C 幕后工作者。
runtime(簡稱運行時)户魏,是一套 純C(C和匯編寫的) 的API。而 OC 就是運行時機制挪挤,也就是在運行時候的一些機制叼丑,其中最主要的是消息機制。
對于 C 語言扛门,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù)鸠信。
OC的函數(shù)調(diào)用成為消息發(fā)送,屬于動態(tài)調(diào)用過程论寨。在編譯的時候并不能決定真正調(diào)用哪個函數(shù)星立,只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調(diào)用。
事實證明:在編譯階段葬凳,OC 可以調(diào)用任何函數(shù)绰垂,即使這個函數(shù)并未實現(xiàn),只要聲明過就不會報錯火焰,只有當運行的時候才會報錯劲装,這是因為OC是運行時動態(tài)調(diào)用的。而 C 語言調(diào)用未實現(xiàn)的函數(shù)就會報錯昌简。
runtime 消息機制
我們寫 OC 代碼占业,它在運行的時候也是轉換成了runtime方式運行的。任何方法調(diào)用本質(zhì):就是發(fā)送一個消息(用runtime發(fā)送消息纯赎,OC 底層實現(xiàn)通過runtime實現(xiàn))谦疾。
消息機制原理:對象根據(jù)方法編號SEL去映射表查找對應的方法實現(xiàn)。
每一個 OC 的方法犬金,底層必然有一個與之對應的runtime方法念恍。
OC-->runtime
簡單示例:
驗證:方法調(diào)用六剥,是否真的是轉換為消息機制?
必須要導入頭文件#import
注解1:我們導入系統(tǒng)的頭文件樊诺,一般用尖括號仗考。
注解2:OC 解決消息機制方法提示步驟【查找build setting-> 搜索msg->objc_msgSend(YES --> NO)】
注解3:最終生成消息機制,編譯器做的事情,最終代碼词爬,需要把當前代碼重新編譯秃嗜,用xcode編譯器,【clang -rewrite-objc main.m查看最終生成代碼】顿膨,示例:cd main.m --> 輸入前面指令锅锨,就會生成 .opp文件(C++代碼)
注解4:這里一般不會直接導入
message.h
示例代碼:OC 方法-->runtime 方法
說明: eat(無參) 和 run(有參) 是 Person模型類中的私有方法「可以幫我調(diào)用私有方法」;
// Person *p = [Person alloc];
// 底層的實際寫法
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
// p = [p init];
p = objc_msgSend(p, sel_registerName("init"));
// 調(diào)用對象方法(本質(zhì):讓對象發(fā)送消息)
//[p eat];
// 本質(zhì):讓類對象發(fā)送消息objc_msgSend(p,@selector(eat));
objc_msgSend([Personclass],@selector(run:),20);
//--------------------------- <#我是分割線#> ------------------------------//
// 也許下面這種好理解一點// id objc = [NSObject alloc];
idobjc = objc_msgSend([NSObjectclass],@selector(alloc));
// objc = [objc init];objc = objc_msgSend(objc,@selector(init));
runtime 方法調(diào)用流程「消息機制」
面試:消息機制方法調(diào)用流程
怎么去調(diào)用eat方法恋沃,對象方法:(保存到類對象的方法列表) 必搞,類方法:(保存到元類(Meta Class)中方法列表)。
1.OC 在向一個對象發(fā)送消息時囊咏,runtime庫會根據(jù)對象的isa指針找到該對象對應的類或其父類中查找方法恕洲。。
2.注冊方法編號(這里用方法編號的好處梅割,可以快速查找)霜第。
3.根據(jù)方法編號去查找對應方法。
4.找到只是最終函數(shù)實現(xiàn)地址户辞,根據(jù)地址去方法區(qū)調(diào)用對應函數(shù)泌类。
補充:一個objc對象的isa的指針指向什么?有什么作用底燎?
每一個對象內(nèi)部都有一個isa指針刃榨,這個指針是指向它的真實類型,根據(jù)這個指針就能知道將來調(diào)用哪個類的方法。
runtime 常見作用
動態(tài)交換兩個方法的實現(xiàn)
動態(tài)添加屬性
實現(xiàn)字典轉模型的自動轉換
發(fā)送消息
動態(tài)添加方法
攔截并替換方法
實現(xiàn) NSCoding 的自動歸檔和解檔
runtime 常用開發(fā)應用場景「工作掌握」
runtime 交換方法
應用場景:當?shù)谌娇蚣?或者 系統(tǒng)原生方法功能不能滿足我們的時候,我們可以在保持系統(tǒng)原有方法功能的基礎上丐枉,添加額外的功能。
需求:加載一張圖片直接用[UIImage imageNamed:@"image"];是無法知道到底有沒有加載成功晴玖。給系統(tǒng)的imageNamed添加額外功能(是否加載圖片成功)。
方案一:繼承系統(tǒng)的類为流,重寫方法.(弊端:每次使用都需要導入)
方案二:使用 runtime呕屎,交換方法.
實現(xiàn)步驟:
1.給系統(tǒng)的方法添加分類
2.自己實現(xiàn)一個帶有擴展功能的方法
3.交換方法,只需要交換一次,
案例代碼:方法+調(diào)用+打印輸出
- (void)viewDidLoad {
[superviewDidLoad];
// 方案一:先搞個分類,定義一個能加載圖片并且能打印的方法
+ (instancetype)imageWithName:(NSString *)name;
// 方案二:交換 imageNamed 和 ln_imageNamed 的實現(xiàn)敬察,就能調(diào)用 imageNamed秀睛,間接調(diào)用 ln_imageNamed 的實現(xiàn)。
UIImage*image = [UIImageimageNamed:@"123"];}#import@implementationUIImage(Image)
/**
load方法: 把類加載進內(nèi)存的時候調(diào)用,只會調(diào)用一次
方法應先交換莲祸,再去調(diào)用
*/
+ (void)load {
// 1.獲取 imageNamed方法地址
// class_getClassMethod(獲取某個類的方法)
Method imageNamedMethod = class_getClassMethod(self,@selector(imageNamed:));
// 2.獲取 ln_imageNamed方法地址
Method ln_imageNamedMethod = class_getClassMethod(self,@selector(ln_imageNamed:));
// 3.交換方法地址蹂安,相當于交換實現(xiàn)方式;
「method_exchangeImplementations 交換兩個方法的實現(xiàn)」method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);}
/**
看清楚下面是不會有死循環(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添加額外功能--加載失敗");
}
return image;
}/**
不能在分類中重寫系統(tǒng)方法imageNamed椭迎,因為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super
所以第二步田盈,我們要 自己實現(xiàn)一個帶有擴展功能的方法.
+ (UIImage *)imageNamed:(NSString *)name {
}
*/@end
// 打印輸出2016-02-1717:52:14.693runtime[12761:543574] runtime添加額外功能--加載成功
總結:我們所做的就是在方法調(diào)用流程第三步的時候畜号,交換兩個方法地址指向。而且我們改變指向要在系統(tǒng)的imageNamed:方法調(diào)用前允瞧,所以將代碼寫在了分類的load方法里简软。最后當運行的時候系統(tǒng)的方法就會去找我們的方法的實現(xiàn)。
runtime 給分類動態(tài)添加屬性
原理:給一個類聲明屬性述暂,其實本質(zhì)就是給這個類添加關聯(lián)痹升,并不是直接把這個值的內(nèi)存空間添加到類存空間。
應用場景:給系統(tǒng)的類添加屬性的時候,可以使用runtime動態(tài)添加屬性方法畦韭。
注解:系統(tǒng)NSObject添加一個分類疼蛾,我們知道在分類中是不能夠添加成員屬性的,雖然我們用了@property艺配,但是僅僅會自動生成get和set方法的聲明察郁,并沒有帶下劃線的屬性和方法實現(xiàn)生成。但是我們可以通過runtime就可以做到給它方法的實現(xiàn)转唉。
需求:給系統(tǒng) NSObject 類動態(tài)添加屬性name字符串绳锅。
案例代碼:方法+調(diào)用+打印
@interfaceNSObject(Property)
// @property分類:只會生成get,set方法聲明,不會生成實現(xiàn),也不會生成下劃線成員屬性
@propertyNSString*name;
@propertyNSString*height;
@end
@implementationNSObject(Property)
- (void)setName:(NSString*)name
{// objc_setAssociatedObject(將某個值跟某個對象關聯(lián)起來,將某個值存儲到某個對象中)
// object:給哪個對象添加屬性// 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動態(tài)添加屬性name==%@",objc.name);
// 打印輸出2016-02-1719:37:10.530runtime[12761:543574] runtime動態(tài)添加屬性--name ==123
總結:其實酝掩,給屬性賦值的本質(zhì),就是讓屬性與一個對象產(chǎn)生關聯(lián)眷柔,所以要給NSObject的分類的name屬性賦值就是讓name和NSObject產(chǎn)生關聯(lián)期虾,而runtime可以做到這一點。
runtime 字典轉模型
字典轉模型的方式:
一個一個的給模型屬性賦值(初學者)驯嘱。
字典轉模型KVC實現(xiàn)
KVC 字典轉模型弊端:必須保證镶苞,模型中的屬性和字典中的key一一對應。
如果不一致鞠评,就會調(diào)用[ setValue:forUndefinedKey:]報key找不到的錯茂蚓。
分析:模型中的屬性和字典的key不一一對應,系統(tǒng)就會調(diào)用setValue:forUndefinedKey:報錯剃幌。
解決:重寫對象的setValue:forUndefinedKey:聋涨,把系統(tǒng)的方法覆蓋,就能繼續(xù)使用KVC负乡,字典轉模型了牍白。
字典轉模型Runtime實現(xiàn)
思路:利用運行時,遍歷模型中所有屬性抖棘,根據(jù)模型的屬性名茂腥,去字典中查找key狸涌,取出對應的值,給模型的屬性賦值(從提醒:字典中取值最岗,不一定要全部取出來)帕胆。
考慮情況:
1.當字典的key和模型的屬性匹配不上。
2.模型中嵌套模型(模型屬性是另外一個模型對象)般渡。
3.數(shù)組中裝著模型(模型的屬性是一個數(shù)組懒豹,數(shù)組中是一個個模型對象)。
注解:根據(jù)上面的三種特殊情況诊杆,先是字典的key和模型的屬性不對應的情況歼捐。不對應有兩種,一種是字典的鍵值大于模型屬性數(shù)量晨汹,這時候我們不需要任何處理豹储,因為runtime是先遍歷模型所有屬性,再去字典中根據(jù)屬性名找對應值進行賦值淘这,多余的鍵值對也當然不會去看了剥扣;另外一種是模型屬性數(shù)量大于字典的鍵值對,這時候由于屬性沒有對應值會被賦值為nil铝穷,就會導致crash钠怯,我們只需加一個判斷即可。考慮三種情況下面一一注解曙聂;
步驟:提供一個NSObject分類晦炊,專門字典轉模型,以后所有模型都可以通過這個分類實現(xiàn)字典轉模型宁脊。
MJExtension字典轉模型實現(xiàn)
底層也是對runtime的封裝断国,才可以把一個模型中所有屬性遍歷出來。(你之所以看不懂榆苞,是MJ封裝了很多層而已_.)稳衬。
這里針對字典轉模型 KVC 實現(xiàn),就不做詳解了坐漏,如果你 對 KVC 詳解使用或是實現(xiàn)原理 不是很清楚的薄疚,可以參考實用「KVC編碼 & KVO監(jiān)聽
字典轉模型 Runtime 方式實現(xiàn):
說明:下面這個示例,是考慮三種情況包含在內(nèi)的轉換示例赊琳,具體可以看圖上的注解
Runtime 字典轉模型
1街夭、runtime 字典轉模型-->字典的 key 和模型的屬性不匹配「模型屬性數(shù)量大于字典鍵值對數(shù)」,這種情況處理如下:
// Runtime:根據(jù)模型中屬性,去字典中取出對應的value給模型屬性賦值// 思路:遍歷模型中所有屬性->使用運行時
+ (instancetype)modelWithDict:(NSDictionary*)dict{
// 1.創(chuàng)建對應的對象
id objc = [[selfalloc] init];
// 2.利用runtime給對象中的屬性賦值
/**
class_copyIvarList: 獲取類中的所有成員變量
Ivar:成員變量
第一個參數(shù):表示獲取哪個類中的成員變量
第二個參數(shù):表示這個類有多少成員變量慨畸,傳入一個Int變量地址莱坎,會自動給這個變量賦值
返回值Ivar *:指的是一個ivar數(shù)組,會把所有成員屬性放在一個數(shù)組中寸士,通過返回的數(shù)組就能全部獲取到檐什。
count: 成員變量個數(shù)
*/
unsignedintcount =0;
// 獲取類中的所有成員變量
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for(inti =0; i < count; i++) {
// 根據(jù)角標碴卧,從數(shù)組取出對應的成員變量
Ivar ivar = ivarList[i];
// 獲取成員變量名字
NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];
// 處理成員變量名->字典中的key(去掉 _ ,從第一個角標開始截取)
NSString*key = [ivarName substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應的
valueidvalue = dict[key];
// 【如果模型屬性數(shù)量大于字典鍵值對數(shù)理,模型屬性會被賦值為nil】
// 而報錯 (could not set nil as the value for the key age.)
if(value) {
// 給模型中屬性賦值[objc setValue:value forKey:key];
}
}
return objc;
}
注解:這里在獲取模型類中的所有屬性名乃正,是采取class_copyIvarList先獲取成員變量(以下劃線開頭) 住册,然后再處理成員變量名->字典中的key(去掉 _ ,從第一個角標開始截取) 得到屬性名。
原因:Ivar:成員變量,以下劃線開頭瓮具,Property 屬性
獲取類里面屬性class_copyPropertyList
獲取類中的所有成員變量class_copyIvarList
{int_a;// 成員變量}@property(nonatomic,assign)NSIntegerattitudes_count;// 屬性這里有成員變量荧飞,就不會漏掉屬性;如果有屬性名党,可能會漏掉成員變量叹阔;
使用runtime字典轉模型獲取模型屬性名的時候,最好獲取成員屬性名Ivar因為可能會有個屬性是沒有setter和getter方法的传睹。
2耳幢、runtime 字典轉模型-->模型中嵌套模型「模型屬性是另外一個模型對象」,這種情況處理如下:
+ (instancetype)modelWithDict2:(NSDictionary*)dict{
// 1.創(chuàng)建對應的對象
id objc = [[selfalloc] init];
// 2.利用runtime給對象中的屬性賦值
unsignedintcount =0;
// 獲取類中的所有成員變量
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for(inti =0; i < count; i++) {
// 根據(jù)角標欧啤,從數(shù)組取出對應的成員變量
Ivar ivar = ivarList[i];
// 獲取成員變量名字
NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];
// 獲取成員變量類型
NSString*ivarType = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 替換: @\"User\" ->
User ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\""withString:@""]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@"withString:@""];
// 處理成員屬性名->字典中的key(去掉 _ ,從第一個角標開始截取)
NSString*key = [ivarName substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應的
value idvalue = dict[key];
//--------------------------- <#我是分割線#> ------------------------------//////
二級轉換:如果字典中還有字典睛藻,也需要把對應的字典轉換成模型
// 判斷下value是否是字典,并且是自定義對象才需要轉換
if([value isKindOfClass:[NSDictionaryclass]] && ![ivarType hasPrefix:@"NS"])
{
// 字典轉換成模型 userDict => User模型, 轉換成哪個模型// 根據(jù)字符串類名生成類對象
Class modelClass =NSClassFromString(ivarType);
if(modelClass)
{
// 有對應的模型才需要轉
// 把字典轉模型
value = [modelClass modelWithDict2:value];
}
}
// 給模型中屬性賦值
if(value) {
[objc setValue:value forKey:key];
}
}return objc;
}
3、runtime 字典轉模型-->數(shù)組中裝著模型「模型的屬性是一個數(shù)組邢隧,數(shù)組中是字典模型對象」店印,這種情況處理如下:
// Runtime:根據(jù)模型中屬性,去字典中取出對應的value給模型屬性賦值
// 思路:遍歷模型中所有屬性->使用運行時
+ (instancetype)modelWithDict3:(NSDictionary*)dic
t{
// 1.創(chuàng)建對應的對象
id objc = [[selfalloc] init];
// 2.利用runtime給對象中的屬性賦值
unsignedintcount =0;
// 獲取類中的所有成員變量
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for(inti =0; i < count; i++)
{
// 根據(jù)角標,從數(shù)組取出對應的成員變量
Ivar ivar = ivarList[i];
// 獲取成員變量名字
NSString*ivarName = [NSStringstringWithUTF8String:ivar_getName(ivar)];
// 處理成員屬性名->字典中的key(去掉 _ ,從第一個角標開始截取)
NSString*key = [ivarName substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應的
valueidvalue = dict[key];
//--------------------------- <#我是分割線#> ------------------------------////
// 三級轉換:NSArray中也是字典倒慧,把數(shù)組中的字典轉換成模型.
// 判斷值是否是數(shù)組
if([value isKindOfClass:[NSArrayclass]])
{
// 判斷對應類有沒有實現(xiàn)字典數(shù)組轉模型數(shù)組的協(xié)議
// arrayContainModelClass 提供一個協(xié)議按摘,只要遵守這個協(xié)議的類,都能把數(shù)組中的字典轉模型
if([selfrespondsToSelector:@selector(arrayContainModelClass)])
{
// 轉換成id類型纫谅,就能調(diào)用任何對象的方法
id idSelf =self;
// 獲取數(shù)組中字典對應的模型
NSString*type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel =NSClassFromString(type);
NSMutableArray*arrM = [NSMutableArrayarray];
// 遍歷字典數(shù)組院峡,生成模型數(shù)組
for(NSDictionary*dictinvalue)
{
// 字典轉模型
id model = [classModel modelWithDict3:dict];
[arrM addObject:model];
}
// 把模型數(shù)組賦值給
valuevalue = arrM;
}
}
// 如果模型屬性數(shù)量大于字典鍵值對數(shù)理,模型屬性會被賦值為nil,而報錯
if(value) {
// 給模型中屬性賦值
[objc setValue:value forKey:key];
}
}return objc;}
![image](http://upload-images.jianshu.io/upload_images/3775802-8e34cd9290a4a944?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
runtime字典轉模型-->數(shù)組中裝著模型 打印輸出
總結:我們既然能獲取到屬性類型系宜,那就可以攔截到模型的那個數(shù)組屬性,進而對數(shù)組中每個模型遍歷并字典轉模型发魄,但是我們不知道數(shù)組中的模型都是什么類型盹牧,我們可以聲明一個方法,該方法目的不是讓其調(diào)用励幼,而是讓其實現(xiàn)并返回模型的類型汰寓。
這里提到的你如果不是很清楚,建議參考我的Demo苹粟,重要的部分代碼中都有相應的注解和文字打印有滑,運行程序可以很直觀的表現(xiàn)。
runtime 其它作用「面試熟悉」
動態(tài)添加方法
應用場景:如果一個類方法非常多嵌削,加載類到內(nèi)存的時候也比較耗費資源毛好,需要給每個方法生成映射表望艺,可以使用動態(tài)給某個類,添加方法解決肌访。
注解:OC 中我們很習慣的會用懶加載找默,當用到的時候才去加載它,但是實際上只要一個類實現(xiàn)了某個方法吼驶,就會被加載進內(nèi)存惩激。當我們不想加載這么多方法的時候,就會使用到runtime動態(tài)的添加方法蟹演。
需求:runtime 動態(tài)添加方法處理調(diào)用一個未實現(xiàn)的方法 和 去除報錯风钻。
案例代碼:方法+調(diào)用+打印輸出
- (void)viewDidLoad {
[superviewDidLoad];
Person *p = [[Person alloc] init];
// 默認person,沒有實現(xiàn)run:方法酒请,可以通過performSelector調(diào)用骡技,但是會報錯。
// 動態(tài)添加方法就不會報錯
[p performSelector:@selector(run:) withObject:@10];}
@implementationPerson
// 沒有返回值,1個參數(shù)
// void,(id,SEL)
void aaa(idself, SEL _cmd,NSNumber*meter) {
NSLog(@"跑了%@米", meter);
}
// 任何方法默認都有兩個隱式參數(shù),self,_cmd(當前方法的方法編號)
// 什么時候調(diào)用:只要一個對象調(diào)用了一個未實現(xiàn)的方法就會調(diào)用這個方法,進行處理
// 作用:動態(tài)添加方法,處理未實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// [NSStringFromSelector(sel) isEqualToString:@"run"];
if(sel ==NSSelectorFromString(@"run:"))
{
// 動態(tài)添加run方法
// class: 給哪個類添加方法
// SEL: 添加哪個方法蚌父,即添加方法的方法編號
// IMP: 方法實現(xiàn) => 函數(shù) => 函數(shù)入口 => 函數(shù)名(添加方法的函數(shù)實現(xiàn)(函數(shù)地址))
// type: 方法類型哮兰,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->
_cmd class_addMethod(self, sel, (IMP)aaa,"v@:@");
returnYES;
}return
[super resolveInstanceMethod:sel];
}@end
// 打印輸出2016-02-1719:05:03.917runtime[12761:543574] runtime動態(tài)添加方法--跑了10米
動態(tài)變量控制
現(xiàn)在有一個Person類,創(chuàng)建 xiaoming對象
動態(tài)獲取 XiaoMing 類中的所有屬性 [當然包括私有]
Ivar *ivar = class_copyIvarList([self.xiaomingclass], &count);
遍歷屬性找到對應name字段
constchar*varName = ivar_getName(var);
修改對應的字段值成20
object_setIvar(self.xiaoMing,var, @"20");
代碼參考
-(void)answer{unsignedintcount =0; Ivar *ivar = class_copyIvarList([self.xiaoMingclass], &count);for(inti =0; i
實現(xiàn)NSCoding的自動歸檔和解檔
如果你實現(xiàn)過自定義模型數(shù)據(jù)持久化的過程苟弛,那么你也肯定明白喝滞,如果一個模型有許多個屬性,那么我們需要對每個屬性都實現(xiàn)一遍encodeObject和decodeObjectForKey方法膏秫,如果這樣的模型又有很多個右遭,這還真的是一個十分麻煩的事情。下面來看看簡單的實現(xiàn)方式缤削。
假設現(xiàn)在有一個Movie類窘哈,有3個屬性。先看下.h文件
// Movie.h文件
//1\. 如果想要當前類可以實現(xiàn)歸檔與反歸檔亭敢,需要遵守一個協(xié)議NSCoding
@interfaceMovie:NSObject
@property(nonatomic,copy)NSString*movieId;
@property(nonatomic,copy)NSString*movieName;
@property(nonatomic,copy)NSString*pic_url;
@end
如果是正常寫法滚婉,.m文件應該是這樣的:
// Movie.m文件
@implementationMovie
- (void)encodeWithCoder:(NSCoder*)aCoder{
[aCoder encodeObject:_movieId forKey:@"id"];
[aCoder encodeObject:_movieName forKey:@"name"];
[aCoder encodeObject:_pic_url forKey:@"url"];}
- (id)initWithCoder:(NSCoder*)aDecoder{
if(self= [superinit])
{
self.movieId = [aDecoder decodeObjectForKey:@"id"];
self.movieName = [aDecoder decodeObjectForKey:@"name"];
self.pic_url = [aDecoder decodeObjectForKey:@"url"];
}
return self;
}
@end
runtime 下Class的各項操作
下面是 runtime 下Class的常見方法 及 帶有使用示例代碼。各項操作帅刀,原著 http://www.reibang.com/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(unsignedinti; i%@",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)在有一個Person類让腹,和person創(chuàng)建的xiaoming對象,有test1和test2兩個方法
獲得類方法
Class PersonClass = object_getClass([Personclass]);SEL oriSEL = @selector(test1);Method oriMethod = _class_getMethod(xiaomingClass, oriSEL);
獲得實例方法
Class PersonClass = object_getClass([xiaomingclass]);SEL oriSEL = @selector(test2);Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
添加方法
BOOLaddSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
替換原方法實現(xiàn)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
交換兩個方法的實現(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指針扣溺,但是這個在OC中好像毫無意義Ivar oneCVIvar = class_getClassVariable([Personclass], name);// 根據(jù)名字得到實例變量的Ivar指針I(yè)var oneIVIvar = class_getInstanceVariable([Personclass], name);// 找到后可以直接對私有變量賦值object_setIvar(_per, oneIVIvar,@"Mike");//強制修改name屬性/ 動態(tài)添加方法:
第一個參數(shù)表示Class cls 類型骇窍;
第二個參數(shù)表示待調(diào)用的方法名稱;
第三個參數(shù)(IMP)myAddingFunction锥余,IMP是一個函數(shù)指針腹纳,這里表示指定具體實現(xiàn)方法myAddingFunction;
第四個參數(shù)表方法的參數(shù),0代表沒有參數(shù)嘲恍;
*/class_addMethod([_perclass],@selector(sayHi), (IMP)myAddingFunction,0);// 交換兩個方法method_exchangeImplementations(method1, method2);// 關聯(lián)兩個對象objc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy)/*
id object :表示關聯(lián)者足画,是一個對象,變量名理所當然也是object
const void *key :獲取被關聯(lián)者的索引key
id value :被關聯(lián)者蛔钙,這里是一個block
objc_AssociationPolicy policy : 關聯(lián)時采用的協(xié)議锌云,有assign,retain吁脱,copy等協(xié)議桑涎,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
/// 獲得某個類的類方法Method class_getClassMethod(Class cls , SEL name)// 獲得某個類的實例對象方法Method class_getInstanceMethod(Class cls , SEL name)// 交換兩個方法的實現(xiàn)voidmethod_exchangeImplementations(Method m1 , Method m2)// 將某個值跟某個對象關聯(lián)起來,將某個值存儲到某個對象中voidobjc_setAssociatedObject(idobject ,constvoidkey ,idvalue ,objc_AssociationPolicy policy)// 利用參數(shù)key 將對象object中存儲的對應值取出來idobjc_getAssociatedObject(idobject ,constvoidkey)// 獲得某個類的所有成員變量(outCount 會返回成員變量的總數(shù))Ivar class_copyIvarList(Class cls ,unsignedintoutCount)// 獲得成員變量的名字constcharivar_getName(Ivar v)// 獲得成員變量的類型constcharivar_getTypeEndcoding(Ivar v)// 獲取類里面所有方法class_copyMethodList(__unsafe_unretainedClass cls,unsignedintoutCount)// 本質(zhì):創(chuàng)建誰的對象// 獲取類里面屬性class_copyPropertyList(__unsafe_unretainedClass cls,unsignedint*outCount)
runtime 幾個參數(shù)概念
以上的幾種方法應該算是runtime在實際場景中所應用的大部分的情況了兼贡,平常的編碼中差不多足夠用了攻冷。
這里在對runtime幾個參數(shù)概念,做一簡單說明
1遍希、objc_msgSend
這是個最基本的用于發(fā)送消息的函數(shù)等曼。
其實編譯器會根據(jù)情況在objc_msgSend,objc_msgSend_stret,凿蒜,objc_msgSendSuper禁谦, 或objc_msgSendSuper_stret四個方法中選擇一個來調(diào)用。如果消息是傳遞給超類废封,那么會調(diào)用名字帶有Super的函數(shù)州泊;如果消息返回值是數(shù)據(jù)結構而不是簡單值時,那么會調(diào)用名字帶有stret的函數(shù)漂洋。
2遥皂、SEL
objc_msgSend函數(shù)第二個參數(shù)類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)刽漂。selector是方法選擇器演训,可以理解為區(qū)分方法的ID,而這個ID的數(shù)據(jù)結構是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串贝咙,你可以用 Objc 編譯器命令@selector()``或者 Runtime系統(tǒng)的sel_registerName函數(shù)來獲得一個SEL類型的方法選擇器。
3庭猩、id
objc_msgSend第一個參數(shù)類型為id乌奇,大家對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結構體包含一個isa指針眯娱,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。
4爬凑、runtime.h里Class的定義
structobjc_class{Class isa OBJC_ISA_AVAILABILITY;//每個Class都有一個isa指針#if!__OBJC2__Class super_class OBJC2_UNAVAILABLE;//父類constcharname OBJC2_UNAVAILABLE;//類名longversion OBJC2_UNAVAILABLE;//類版本longinfo OBJC2_UNAVAILABLE;//!!供運行期使用的一些位標識徙缴。如:CLS_CLASS (0x1L)表示該類為普通class; CLS_META(0x2L)表示該類為metaclass等(runtime.h中有詳細列出)longinstance_size OBJC2_UNAVAILABLE;//實例大小structobjc_ivar_listivarsOBJC2_UNAVAILABLE;//存儲每個實例變量的內(nèi)存地址structobjc_method_listmethodListsOBJC2_UNAVAILABLE;//!!根據(jù)info的信息確定是類還是實例,運行什么函數(shù)方法等structobjc_cachecacheOBJC2_UNAVAILABLE;//緩存structobjc_protocol_listprotocolsOBJC2_UNAVAILABLE;//協(xié)議#endif} OBJC2_UNAVAILABLE;
可以看到運行時一個類還關聯(lián)了它的超類指針,類名于样,成員變量疏叨,方法,緩存穿剖,還有附屬的協(xié)議蚤蔓。
在objc_class結構體中:``ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針糊余。也就是說可以動態(tài)修改*methodLists的值來添加成員方法秀又,這也是Category實現(xiàn)的原理。
什么是 method swizzling(俗稱黑魔法)
簡單說就是進行方法交換
在Objective-C中調(diào)用一個方法贬芥,其實是向一個對象發(fā)送消息吐辙,查找消息的唯一依據(jù)是selector的名字。利用Objective-C的動態(tài)特性蘸劈,可以實現(xiàn)在運行時偷換selector對應的方法實現(xiàn)昏苏,達到給方法掛鉤的目的
每個類都有一個方法列表,存放著方法的名字和方法實現(xiàn)的映射關系威沫,selector的本質(zhì)其實就是方法名贤惯,IMP有點類似函數(shù)指針,指向具體的Method實現(xiàn)棒掠,通過selector就可以找到對應的IMP孵构。
selector --> 對應的IMP
交換方法的幾種實現(xiàn)方式
利用method_exchangeImplementations交換兩個方法的實現(xiàn)
利用class_replaceMethod替換方法的實現(xiàn)
利用method_setImplementation來直接設置某個方法的IMP。
交換方法
這里可以參考簡友這篇:【Runtime Method Swizzling開發(fā)實例匯總】http://www.reibang.com/p/f6dad8e1b848
最后一道面試題的注解
下面的代碼輸出什么?
@implementationSon:NSObject- (id)init{self= [superinit];if(self) {NSLog(@"%@",NSStringFromClass([selfclass]));NSLog(@"%@",NSStringFromClass([superclass])); }returnself;}@end
先思考一下句柠,會打印出來什么?
關注我的更多干貨分享_.
答案:都輸出 Son
class獲取當前方法的調(diào)用者的類浦译,superClass獲取當前方法的調(diào)用者的父類,super僅僅是一個編譯指示器溯职,就是給編譯器看的精盅,不是一個指針。
本質(zhì):只要編譯器看到super這個標志谜酒,就會讓當前對象去調(diào)用父類方法叹俏,本質(zhì)還是當前對象在調(diào)用
這個題目主要是考察關于objc中對self和super的理解:
self是類的隱藏參數(shù),指向當前調(diào)用方法的這個類的實例僻族。而super本質(zhì)是一個編譯器標示符粘驰,和self是指向的同一個消息接受者
當使用self調(diào)用方法時,會從當前類的方法列表中開始找述么,如果沒有蝌数,就從父類中再找;
而當使用super時度秘,則從父類的方法列表中開始找顶伞。然后調(diào)用父類的這個方法
調(diào)用[self class]時,會轉化成objc_msgSend函數(shù)
idobjc_msgSend(idself, SEL op, ...)- 調(diào)用 [superclass]
時,會轉化成 objc_msgSendSuper
函數(shù).idobjc_msgSendSuper(structobjc_super *super, SEL op, ...)第一個參數(shù)是 objc_super 這樣一個結構體唆貌,其定義如下structobjc_super { __unsafe_unretainedidreceiver; __unsafe_unretainedClass super_class; };第一個成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個參數(shù)self第二個成員是記錄當前類的父類是什么滑潘,告訴程序從父類中開始找方法,找到方法后锨咙,最后內(nèi)部是使用 objc_msgSend(objc_super->receiver,@selector(class))去調(diào)用语卤, 此時已經(jīng)和[selfclass]調(diào)用相同了,故上述輸出結果仍然返回 Sonobjc Runtime 開源代碼對- (Class)class方法的實現(xiàn)-(Class)class{returnobject_getClass(self); }
Runtime 模塊博文推薦 (??數(shù)量較多)
作者Runtime 模塊推薦閱讀博文
西木完整總結http://www.reibang.com/p/6b905584f536
天口三水羊objc_msgSendhttp://www.reibang.com/p/9e1bc8d890f9
夜千尋墨詳解http://www.reibang.com/p/46dd81402f63
袁崢Seemygo快速上手http://www.reibang.com/p/e071206103a4
鄭欽洪_實現(xiàn)自動化歸檔http://www.reibang.com/p/bd24c3f3cd0a
HenryCheng消息機制http://www.reibang.com/p/f6300eb3ec3d
賣報的小畫家SureMethod Swizzling開發(fā)實例匯總http://www.reibang.com/p/f6dad8e1b848
滕大鳥OC最實用的runtime總結http://www.reibang.com/p/ab966e8a82e2
黑花白花Runtime在實際開發(fā)中的應用http://www.reibang.com/p/851b21870d91
Runtime & Runloop 面試最常問到的題整理【建議看】
說明:此面試題針對性的摘錄整理酪刀,只為方便 在面試路上準備的你 粹舵,會注有原文。
1蓖宦、整理原文:2017年5月iOS招人心得(附面試題)
Runtime
objc在向一個對象發(fā)送消息時齐婴,發(fā)生了什么?
什么時候會報unrecognized selector錯誤稠茂?iOS有哪些機制來避免走到這一步柠偶?
能否向編譯后得到的類中增加實例變量?能否向運行時創(chuàng)建的類中添加實例變量睬关?為什么诱担?
runtime如何實現(xiàn)weak變量的自動置nil?
給類添加一個屬性后电爹,在類結構體里哪些元素會發(fā)生變化蔫仙?
RunLoop
runloop是來做什么的?runloop和線程有什么關系丐箩?主線程默認開啟了runloop么摇邦?子線程呢?
runloop的mode是用來做什么的屎勘?有幾種mode施籍?
為什么把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循環(huán)以后,滑動scrollview的時候NSTimer卻不動了概漱?
蘋果是如何實現(xiàn)Autorelease Pool的丑慎?
//-------------------- 【我是分割線】 ---------------------//
整理原文:2017年iOS面試題總結,附上答案
Runtime
01
問題:objc在向一個對象發(fā)送消息時瓤摧,發(fā)生了什么竿裂?
解答:根據(jù)對象的 isa 指針找到類對象 id,在查詢類對象里面的 methodLists 方法函數(shù)列表照弥,如果沒有在好到腻异,在沿著 superClass ,尋找父類,再在父類 methodLists 方法列表里面查詢这揣,最終找到 SEL ,根據(jù) id 和 SEL 確認 IMP(指針函數(shù)),在發(fā)送消息悔常;
03
問題:什么時候會報unrecognized selector錯誤敢会?iOS有哪些機制來避免走到這一步?
解答:當發(fā)送消息的時候这嚣,我們會根據(jù)類里面的 methodLists 列表去查詢我們要動用的SEL,當查詢不到的時候塞俱,我們會一直沿著父類查詢姐帚,當最終查詢不到的時候我們會報unrecognized selector錯誤,當系統(tǒng)查詢不到方法的時候障涯,會調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel動態(tài)解釋的方法來給我一次機會來添加罐旗,調(diào)用不到的方法∥ǖ或者我們可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法來告訴系統(tǒng)九秀,該調(diào)用什么方法,一來保證不會崩潰粘我。
04
問題:能否向編譯后得到的類中增加實例變量鼓蜒?能否向運行時創(chuàng)建的類中添加實例變量?為什么征字?
解答:1都弹、不能向編譯后得到的類增加實例變量 2、能向運行時創(chuàng)建的類中添加實例變量匙姜〕┫幔【解釋】:1. 編譯后的類已經(jīng)注冊在 runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內(nèi)存大小已經(jīng)確定,runtime會調(diào)用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.所以不能向存在的類中添加實例變量氮昧。2. 運行時創(chuàng)建的類是可以添加實例變量框杜,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
05
問題:runtime如何實現(xiàn)weak變量的自動置nil袖肥?
解答:runtime 對注冊的類咪辱, 會進行布局,對于 weak 對象會放入一個 hash 表中昭伸。 用 weak 指向的對象內(nèi)存地址作為 key梧乘,當此對象的引用計數(shù)為0的時候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a庐杨,那么就會以a為鍵选调, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象灵份,從而設置為 nil仁堪。
06
問題:給類添加一個屬性后,在類結構體里哪些元素會發(fā)生變化填渠?
解答:instance_size :實例的內(nèi)存大邢夷簟鸟辅;objc_ivar_list *ivars:屬性列表
RunLoop
01
問題:runloop是來做什么的?runloop和線程有什么關系莺葫?主線程默認開啟了runloop么匪凉?子線程呢?
解答:runloop: 從字面意思看:運行循環(huán)捺檬、跑圈再层,其實它內(nèi)部就是do-while循環(huán),在這個循環(huán)內(nèi)部不斷地處理各種任務(比如Source堡纬、Timer聂受、Observer)事件。runloop和線程的關系:一個線程對應一個RunLoop烤镐,主線程的RunLoop默認創(chuàng)建并啟動蛋济,子線程的RunLoop需手動創(chuàng)建且手動啟動(調(diào)用run方法)。RunLoop只能選擇一個Mode啟動炮叶,如果當前Mode中沒有任何Source(Sources0碗旅、Sources1)、Timer悴灵,那么就直接退出RunLoop扛芽。
02
問題:runloop的mode是用來做什么的?有幾種mode积瞒?
解答:model:是runloop里面的運行模式川尖,不同的模式下的runloop處理的事件和消息有一定的差別。系統(tǒng)默認注冊了5個Mode:(1)kCFRunLoopDefaultMode: App的默認 Mode茫孔,通常主線程是在這個 Mode 下運行的叮喳。(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動缰贝,保證界面滑動時不受其他 Mode 影響馍悟。(3)UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用剩晴。(4)GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode锣咒,通常用不到。(5)kCFRunLoopCommonModes: 這是一個占位的 Mode赞弥,沒有實際作用毅整。注意iOS 對以上5中model進行了封裝 NSDefaultRunLoopMode、NSRunLoopCommonModes
03
問題:為什么把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循環(huán)以后绽左,滑動scrollview的時候NSTimer卻不動了悼嫉?
解答:nstime對象是在 NSDefaultRunLoopMode下面調(diào)用消息的,但是當我們滑動scrollview的時候拼窥,NSDefaultRunLoopMode模式就自動切換到UITrackingRunLoopMode模式下面戏蔑,卻不可以繼續(xù)響應nstime發(fā)送的消息蹋凝。所以如果想在滑動scrollview的情況下面還調(diào)用nstime的消息,我們可以把nsrunloop的模式更改為NSRunLoopCommonModes.
04
問題:蘋果是如何實現(xiàn)Autorelease Pool的总棵?
解答:Autorelease Pool作用:緩存池鳍寂,可以避免我們經(jīng)常寫relase的一種方式。其實就是延遲release情龄,將創(chuàng)建的對象伐割,添加到最近的autoreleasePool中,等到autoreleasePool作用域結束的時候刃唤,會將里面所有的對象的引用計數(shù)器 - autorelease.
后續(xù)遇到針對 runtime&runloop常面相關,會及時在作者博客補充 ~