runtime簡(jiǎn)介
Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語(yǔ)言 API砰碴,是 iOS 系統(tǒng)的核心之一兽埃。開(kāi)發(fā)者在編碼過(guò)程中,可以給任意一個(gè)對(duì)象發(fā)送消息涡相,在編譯階段只是確定了要向接收者發(fā)送這條消息哲泊,而接受者將要如何響應(yīng)和處理這條消息,那就要看運(yùn)行時(shí)來(lái)決定了催蝗。 C語(yǔ)言中攻旦,在編譯期,函數(shù)的調(diào)用就會(huì)決定調(diào)用哪個(gè)函數(shù)生逸。 而OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過(guò)程且预,在編譯期并不能決定真正調(diào)用哪個(gè)函數(shù)槽袄,只有在真正運(yùn)行時(shí)才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface Object {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
- Objective-C 對(duì)象都是 C 語(yǔ)言結(jié)構(gòu)體實(shí)現(xiàn)的锋谐,所有的對(duì)象都會(huì)包含一個(gè)isa_t類型的結(jié)構(gòu)體
- objc_class繼承于objc_object遍尺。所以在objc_class中也會(huì)包含isa_t類型的結(jié)構(gòu)體isa。至此涮拗,可以得出結(jié)論:Objective-C 中類也是一個(gè)對(duì)象乾戏。
- 我們可以認(rèn)為id中的isa指針指向的是一個(gè)類對(duì)象,并且在Class結(jié)構(gòu)體中的isa指針指向元類
對(duì)象的實(shí)例方法調(diào)用時(shí)三热,通過(guò)對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn)鼓择。
類對(duì)象的類方法調(diào)用時(shí),通過(guò)類的 isa 在元類中獲取方法的實(shí)現(xiàn)就漾。
SEL
typedef struct objc_selector *SEL;
objc_selector是一個(gè)映射到方法的C字符串抑堡。SEL是系統(tǒng)在編譯過(guò)程中摆出,會(huì)根據(jù)方法的名字以及參數(shù)序列生成一個(gè)用來(lái)區(qū)分這個(gè)方法的唯一ID編號(hào),這個(gè) ID 就是 SEL 類型的首妖。我們需要注意的是偎漫,不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器有缆。由于這點(diǎn)特性象踊,也導(dǎo)致了OC不支持函數(shù)重載温亲。
獲取SEL的幾種方法:
SEL aSel = @selector(didReceiveMemoryWarning);
SEL a_sel = NSSelectorFromString(@"didReceiveMemoryWarning");
SEL a_Sel = sel_registerName("didReceiveMemoryWarning");
NSLog(@"%p___%p___%p",aSel,a_sel,a_Sel);
Method
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; //方法名
char *method_types OBJC2_UNAVAILABLE; //參數(shù)類型以及返回值類型編碼
IMP method_imp OBJC2_UNAVAILABLE; //方法實(shí)現(xiàn)指針
}
獲取方法
// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
IMP
IMP即Implementation,是一個(gè)函數(shù)指針通危,指向的是函數(shù)的具體實(shí)現(xiàn)铸豁。在runtime中消息傳遞和轉(zhuǎn)發(fā)的目的就是為了找到IMP,并執(zhí)行函數(shù)菊碟。
獲取IMP的方法:
//通過(guò)Method獲取IMP
IMP method_getImplementation(Method m);
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
runtime消息發(fā)送與轉(zhuǎn)發(fā)
- NilTest是用來(lái)檢測(cè)是否為nil的节芥,如果檢測(cè)方法的接受者是nil,那么系統(tǒng)會(huì)自動(dòng)clean并且return逆害。
- Cache的作用主要是為了優(yōu)化方法調(diào)用的性能头镊。當(dāng)對(duì)象receiver調(diào)用方法message時(shí),首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(yīng)的類魄幕,然后在類的methodLists中搜索方法相艇,如果沒(méi)有找到,就使用super_class指針到父類中的methodLists查找纯陨,一旦找到就調(diào)用方法坛芽。如果沒(méi)有找到,有可能消息轉(zhuǎn)發(fā)翼抠,也可能忽略它咙轩。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用阴颖,占總調(diào)用次數(shù)的80%活喊。所以使用Cache來(lái)緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí)量愧,優(yōu)先在Cache查找钾菊,如果沒(méi)有找到,再到methodLists查找偎肃。
- MethodTableLookup 可以算是個(gè)接口層宏煞烫,主要用于保存環(huán)境與準(zhǔn)備參數(shù),來(lái)調(diào)用 __class_lookupMethodAndLoadCache3函數(shù)
- __class_lookupMethodAndLoadCache3函數(shù)也是個(gè)接口層(C編寫(xiě))软棺,此函數(shù)提供相應(yīng)參數(shù)配置红竭,實(shí)際功能在lookUpImpOrForward函數(shù)中。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
retry:
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Stop searching, but don't cache yet; call method resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
_class_resolveMethod(cls, sel, inst);
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help. Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
return imp;
}
- 調(diào)用realizeClass方法是申請(qǐng)class_rw_t的可讀寫(xiě)空間喘落。
- _class_initialize是類初始化的過(guò)程茵宪。
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
- 這個(gè)函數(shù)首先判斷是否是meta-class類,如果不是元類瘦棋,就執(zhí)行_class_resolveInstanceMethod稀火,如果是元類,執(zhí)行_class_resolveClassMethod赌朋。
- 為什么查找類方法的實(shí)現(xiàn)過(guò)程中會(huì)查看resolveInstanceMethod的實(shí)現(xiàn)凰狞。
其根本原因?yàn)?NSObject 為root meta class類的根類篇裁,而root meta class 為所有其他meta class的 ISA指向的類。在類方法的巡查過(guò)程中赡若,通過(guò)meta class的繼承關(guān)系會(huì)最終找到NSObject類达布,所以在NSObject 的resolveInstanceMethod添加方法決議實(shí)現(xiàn)代碼,并且在類方法(+號(hào)方法)的決議過(guò)程中檢測(cè)該實(shí)現(xiàn)是合理的逾冬。
消息轉(zhuǎn)發(fā)
動(dòng)態(tài)方法解析
+resolveInstanceMethod:(實(shí)例方法)或者+resolveClassMethod:(類方法);我們有機(jī)會(huì)為該未知消息新增一個(gè)”處理方法””,不過(guò)使用該方法的前提是我們已經(jīng)實(shí)現(xiàn)了該”處理方法”
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"methodO:"]) {
Method addM = class_getInstanceMethod([self class], sel_registerName("functionMethodAddO:"));
class_addMethod([self class], sel, method_getImplementation(addM), method_getTypeEncoding(addM));
}
return [super resolveInstanceMethod:sel];
}
備用接收者
如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法黍聂,并返回一個(gè)非nil的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者身腻,且消息會(huì)被分發(fā)到這個(gè)對(duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorString = NSStringFromSelector(aSelector);
Class cls = NSClassFromString(@"MrHelp");
// 將消息轉(zhuǎn)發(fā)給MrHelp類來(lái)處理
if ([selectorString isEqualToString:@"methodTw"]) {
return [cls new];
}
return [super forwardingTargetForSelector:aSelector];
}
完整消息轉(zhuǎn)發(fā)
運(yùn)行時(shí)系統(tǒng)會(huì)在這一步給消息接收者最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象产还。對(duì)象會(huì)創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中嘀趟,包括selector脐区,目標(biāo)(target)和參數(shù)。在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能她按,我們可以對(duì)消息的內(nèi)容進(jìn)行修改牛隅,比如追回一個(gè)參數(shù)等,然后再去觸發(fā)消息
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
Class cls = NSClassFromString(@"MrHelp");
NSMethodSignature *singature= [super methodSignatureForSelector:aSelector];
if (!singature) {
singature = [cls instanceMethodSignatureForSelector:sel_registerName("universalMethod:AndClass:")];
}
return singature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selectorString = NSStringFromSelector(anInvocation.selector);
Class cls = NSClassFromString(@"MrHelp");
id methodStr =selectorString;
id className = [anInvocation.target class];
[anInvocation setArgument:&methodStr atIndex:2];//第一個(gè)參數(shù)
[anInvocation setArgument:&className atIndex:3];//第而個(gè)參數(shù)
[anInvocation setSelector:sel_registerName("universalMethod:AndClass:")];//universalMethod:AndClass:AndArg:
[anInvocation invokeWithTarget:[cls new]];
}