一、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:這兩個方法。