【ios學習】淺談Runtime

一、Runtime基石:Objective-C對象模型

1、對象

每一個對象都是類的實例, 類中保存對象的方法列表;當一個對象方法被調用時,類會首先查找它本身是否有該方法的實現,如果沒有,則會向它的父類查找該方法,直到NSObject(根類);

類是元類 (metaclass) 的實例;元類保存類方法列表;當一個類方法被調用時,元類會首先查找它本身是否有該類方法的實現,如果沒有,則會向它的父類查找該方法,直到NSObject(根類);

2、isa指針

對象的isa指針指向所屬的類,類的isa指針指向所屬的元類;所有的元類的isa指針都會指向一個根元類 (root metaclass)。根元類的isa指針指向自己,行成了一個閉環。

在64 位 CPU 下,isa 的內部結構有變化。具體查看用?isa 承載對象的類信息

對象、isa指針、類、元類、根元類的關系如下圖:

3、對象布局

實例變量(包括父類)都保存在對象本身的存儲空間內;實例方法保存在中,類方法保存在元類中;父類的實例方法保存在各級 super class 中,父類的類方法保存在各級 super meta class;

//對象組成?--start--

isa?pointer

rootClass's?vars

penultimate?superClass's?vars

...

superClass's?vars

Class's?vars

//對象組成?--end--

typedef?struct?objc_class?*Class;

?//類的結構

?struct?objc_class{

??struct?objc_class*?isa;??????????????????????????????//指向元類

??struct?objc_class*?super_class;????????????????//指向父類

??const?char*?name;

??long?version;

??long?info;

??long?instance_size;

??struct?objc_ivar_list*?ivars;?????????????????????????//實例變量列表

??struct?objc_method_list**?methodLists;??????//方法列表

??struct?objc_cache*?cache;

??struct?objc_protocol_list*?protocols;???????????//協議列表

};

//實例變量的結構

struct?objc_ivar?{

????char?*ivar_name??OBJC2_UNAVAILABLE;

????char?*ivar_type??OBJC2_UNAVAILABLE;

????int?ivar_offset??OBJC2_UNAVAILABLE;

#ifdef?__LP64__

????int?space????????OBJC2_UNAVAILABLE;

#endif

}

說明1:對象中保存指向類的isa指針 以及 各級的 實例變量(ivar),這個內存結構在編譯時就確定下來了,不能在編譯時給對象增加實例變量。

說明2:類的內存布局有isa指針、super_class指針、實例變量列表、方法列表和協議列表,其中實例變量(var)包含了變量的名稱、類型、偏移等。

二、Runtime核心:消息發送和轉發

Runtime賦予了OC了諸多動態特性,使其可以在運行時可以做一些事情;主要表現為:動態類型(在運行時才檢查對象類型)和動態綁定(接到消息后,由運行環境決定執行哪部分代碼)

1、消息發送(Message)

Objective-C 中的方法調用,實質上是在底層用objc_msgSend()實現消息發送,其核心在于:根據SEL(選擇器)開始找到IMP;其中SEL是實例方法的指針,可以看做方法名字符串;IMP是函數指針,指向方法實現的地址。

//調用方法??

[obj?doSomething];

//在編譯時候轉換

objc_msgSend(obj,@selector(doSomething))

objc_msgSend的定義如下:

//?self是接收者,接收該消息的類的實例

????//?_cmd是選擇器,要處理的消息的selector

?????//?...?是需傳入的參數,參數個數不定

????objc_msgSend(id?self,?SEL?_cmd,?...)

objc_msgSend的發送流程:先在Class中的緩存查找imp(沒緩存則初始化緩存),如果沒找到,則向父類的Class查找。如果一直查找到根類仍舊沒有實現,就走消息轉發(_objc_msgForward)了。

給nil發送消息不會有什么作用,但是返回值有些區別,具體如下:

a)?如果方法返回值是?對象,返回nil

b)?如果方法返回值是?指針類型,其指針大小為小于或者等于sizeof(void*),float,double,long?double?或者?long?long?的整型標量

c)?如果方法返回值是?結構體,發送給?nil?的消息將返回0。結構體中各個字段的值將都是0。

d)?如果方法返回值不是?上述提到的幾種情況,那么發送給?nil?的消息的返回值將是未定義的。

2-1、消息轉發(Message Forwarding)

消息轉發解決的是:查找IMP(方法實現)失敗后的處理;經歷動態方法解析備用接收者完整的消息轉發三個過程,其流程如下圖:

動態方法解析:接收到未知消息時,Runtime向當前類發送+resolveInstanceMethod:或+resolveClassMethod:消息,在這里可以添加缺失的方法,返回YES,重新發送消息,否則繼續下一步;

備用接收者動態方法解析中沒能處理,Runtime會向forwardingTargetForSelector:發消息,如果該方法返回了一個非nil或非self對象,恰好該對象實現了這個方法,那么該對象就成了消息的接收者,消息就被分發到該對象。

完整消息轉發:前兩個都沒能處理好,Runtime發送methodSignatureForSelector:消息,獲取selector對應方法的簽名;如果有方法簽名返回,則根據方法簽名創建描述消息的NSInvocation,向當前對象發送forwardInvocation:消息;如果沒有方法簽名返回,返回nil,向當前對象發送doesNotRecognizeSelector:消息,應用Crash退出。

2-2、避免消息轉發的辦法

在消息轉發三個過程中,未知消息的處理過程越往后,代價越大;一般我們可以這么做 盡可能避免消息轉發,可以這么做:

調用delegate 方法前檢查方法是否實現(respondsToSelector:), 只有實現了(respondsToSelector:返回YES) ,才去真正調用delegate 方法。

if([self.delegate?respondsToSelector:?@selector(sayHello)])?{

????[self.delegate?sayHello];

}

直接調用方法,少用performSelector:;因為在直接調用方法時,編譯自動校驗,如果方法不存在,編譯器會直接報錯;而使用performSelector:的話一定是在運行時候才能發現,如果此方法不存在就會崩潰。

//直接使用方法調用,少使用performSelector

[dog?sayHello];

//?[dog?performSelector:@selector(sayHello)?withObject:nil];

使用performSelector:,最好先判斷方法是否實現(respondsToSelector:),只有實現了(respondsToSelector:返回YES) ,才去調用performSelector:方法。

//respondsToSelector:和performSelector:組合使用

????if?([dog?respondsToSelector:@selector(sayHello)])?????????{

????[dog?performSelector:@selector(sayHello)];

?}

強制類型轉換,先判斷對象是否屬于強制轉換后的類

if([data?isKindOfClass:[NSDictionary?class]]){

??//

}

三、Runtime特性和應用

1、分類(Category)

原理:對象的方法定義都保存在類的可變區域中,修改methodLists指針指向的指針的值,就可以實現動態地為某一個類增加成員方法。(但是對象布局在編譯時候就固定了,結構體的大小并不能動態變化,在運行時不能增加實例變量)。

通過關聯objc_setAssociatedObject?和?objc_getAssociatedObject方法可以變相地給對象增加實例變量,并不會真正改變了對象的內存結構。

通過Category新增的方法,會插入到方法列表的前部;如果有和原來方法重名,在運行時,順序查找時,一旦找到對應名字的方法,就不再查找,導致原來方法得不到機會,這是Category新增的方法和原方法重名,原有方法失效的原因

作用:給現有的類添加方法;將一個類的實現拆分成多個獨立的源文件;聲明私有的方法。

2、關聯對象(Associated Objects)

原理:Category不能給一個已有類添加實例變量,但是可以通過關聯對象添加屬性;但是關聯對象不會改變對象的內存布局,新增的屬性是添加到和對象地址關聯的哈希表中;

Associated Objects 相關的三個方法

objc_setAssociatedObject????//添加關聯對象

objc_getAssociatedObject????//獲取關聯對象

objc_removeAssociatedObjects??//?刪除所有關聯對象

作用:為現有的類添加私有變量以幫助實現細節;為現有的類添加公有屬性;為 KVO 創建一個關聯的觀察者

3、方法混寫(Method Swizzling)

原理:在運行時交換方法實現(IMP)

作用:可以利用它hook原有的方法,插入自己的業務需求,

4、鍵值觀察(KVO)

觀察者模式在Objective-C的應用之一,借助Runtime特性,實現自動鍵值觀察;使用了isa swizzling機制。具體描述如下:

當某個類的對象第一次被觀察時,系統就會在運行期動態地創建該類的一個子類,在這個子類中重寫基類中被觀察屬性的 setter 方法,實現真正的通知機制;

派生類還重寫了 class 方法以“欺騙”外部調用者,系統將對象的 isa 指針指向這個新誕生的子類,實質上這個對象就成為該派生類的對象了,因而在該對象上對 setter 的調用就會調用重寫的 setter,從而激活鍵值通知機制。

此外,派生類還重寫了 dealloc 方法來釋放資源。

說明:KVC(鍵值編碼)是不通過存取方法,而通過屬性名稱字符串間接訪問屬性的機制,沒有用到isa swizzling機制。

5、NSProxy

OC是單繼承的,但是可以利用NSProxy實現一下“偽多繼承”,具體參考NSProxy——少見卻神奇的類

項目中,主要是利用NSProxy做消息轉發的代理類,如弱引用代理類,可以打破循環引用。

@interface?FLWeakProxy?:?NSProxy

+?(instancetype)weakProxyForObject:(id)targetObject;

@end

@interface?FLWeakProxy?()

@property?(nonatomic,?weak)?id?target;

@end

@implementation?FLWeakProxy

#pragma?mark?Life?Cycle

//類沒有定義默認的init方法.

+?(instancetype)weakProxyForObject:(id)targetObject{

????FLWeakProxy?*weakProxy?=?[FLWeakProxy?alloc];

????weakProxy.target?=?targetObject;

????return?weakProxy;

}

#pragma?mark?Forwarding?Messages

-?(id)forwardingTargetForSelector:(SEL)selector{

//?Keep?it?lightweight:?access?the?ivar?directly

return?_target;

}

-?(void)forwardInvocation:(NSInvocation?*)invocation{

????void?*nullPointer?=?NULL;

????[invocation?setReturnValue:&nullPointer];

}

-?(NSMethodSignature?*)methodSignatureForSelector:(SEL)selector{

????return?[NSObject?instanceMethodSignatureForSelector:@selector(init)];

}

@end

說明: NSProxy非常適合做消息轉發的代理類,能自動轉發中定義的接口和NSObject的Category中定義的方法,如果使用NSObject來做,不能自動轉發NSObject的Category中定義、respondsToSelector:、isKindOfClass:這兩個方法。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容