Objective-C這門語言,眾所周知,是對C進(jìn)行了擴(kuò)展,具體來說進(jìn)行了兩個(gè)方面的擴(kuò)展,面向?qū)ο蟮奶匦院蛃malltalk中的消息傳遞。而消息傳遞機(jī)制歸根結(jié)底是建立在Runtime庫上。正是這種機(jī)制,決定了Objective-C是一門動態(tài)語言,而同樣是對C擴(kuò)展的C++,是靜態(tài)的。Objective-C將很多決定性的操作依靠Runtime在運(yùn)行時(shí)處理,而C++僅僅在編譯時(shí)就決定了如何處理
消息傳遞
在我們所熟悉的調(diào)用方法背后,最終都是以消息傳遞的方式進(jìn)行處理,例如[object method],從表面上來看,是object調(diào)用了method方法,實(shí)際上,在運(yùn)行時(shí),是給object發(fā)送了一條method消息,這條消息不一定非要object來處理,也可以轉(zhuǎn)發(fā)給其他的對象處理,也可以不進(jìn)行處理,這些種種操作,都是利用Runtime在運(yùn)行時(shí)處理的。
對于[object method]的調(diào)用,編譯器會將其編譯成一行C語言的函數(shù):
objc_msgSend(object, @selector(method));
消息傳遞的步驟
了解了消息傳遞之后,需要進(jìn)一步知道消息傳遞的具體步驟:
- 先查看method方法是不是需要被忽略
- 查看object對象是否為nil(Objective-C中允許空對象調(diào)用任何方法的原因)
- 查看緩存中是否存在方法,系統(tǒng)把近期發(fā)送過的消息記錄在其中,蘋果認(rèn)為這樣可以提高效率
- 如果緩存中沒有命中,那么查找該類的方法表,依次從后往前查找
- 如果沒有找到,則進(jìn)入父類查找
- 如果到了根類還是沒有找到的話,那么就進(jìn)入動態(tài)解析
Runtime中的基本類型
以上過程雖然讀起來蠻容易理解的,但是我們還得搞清楚Runtime是通過什么進(jìn)行上述操作的,這時(shí)候就需要對Runtime的一些基本類型進(jìn)行了解,我們可以在objc/objc.h中看到以下這些定義
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
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;
#endif
};
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_method method_list[1];
};
struct objc_method {
SEL method_name;
char *method_types; /* a string representing argument/return types */
IMP method_imp;
};
不難發(fā)現(xiàn),基本每個(gè)結(jié)構(gòu)都是C語言中的結(jié)構(gòu)體,object_object對應(yīng)著object,object_class對應(yīng)著對象所屬于的類,我們先把目光主要集中在object_class這個(gè)結(jié)構(gòu)體上,可以發(fā)現(xiàn)幾個(gè)上述步驟涉及到的東西:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
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;
#endif
};
再次理解消息傳遞的步驟
理解了Runtime的基本結(jié)構(gòu)后,我們再次用專業(yè)的角度來理解消息傳遞:
- 查看method方法是否需要被忽略
- 查看object對象是否為nil
- 通過objc_object中的isa指針,找到該object的objc_class,然后查看objc_cache類型的cache成員中是否有這個(gè)方法,如果有,則找到objc_method中的IMP類型(函數(shù)指針)的成員method_imp去找到實(shí)現(xiàn)內(nèi)容,并執(zhí)行。
- 如果沒有找到,則查看objc_method_list類型的成員methodLists中是否有該方法
- 如果沒有找到,則通過Class類型的成員super_class找到父類的objc_class,進(jìn)行查找
- 要是還沒有找到,則進(jìn)入動態(tài)解析
如果是調(diào)用類方法呢
再次查看這個(gè)objc_class的結(jié)構(gòu):
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
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;
#endif
};
剛剛第一次看的時(shí)候可能沒有注意到第一個(gè)成員,第一個(gè)成員指向的是結(jié)構(gòu)是metaclass,其中包含靜態(tài)成員變量和靜態(tài)方法(類方法),同時(shí)也包含了一個(gè)isa成員,都指向了父類的metaclass,如果是根類,則指向自己。所以如果是調(diào)用類方法的話,那么就會利用objc_class中的成員isa找到metaclass,然后尋找方法,沒有找到的話則仍然進(jìn)入動態(tài)解析。
動態(tài)解析
通過第二次的理解,對于消息傳遞有了一個(gè)清晰的了解,我們繼續(xù)來研究消息傳遞最后一步的動態(tài)解析。正常我們?nèi)绻{(diào)用了一個(gè)沒有實(shí)現(xiàn)的方法,那么程序會崩潰,并且拋出unrecognized selector to ...的異常,但是利用Runtime,我們可以有三次機(jī)會避免程序崩潰,先通過一張圖來大致了解下過程:

我們具體看下三種方法:
-
resovleInstanceMethod:
void otherEat(id self, SEL cmd) {
NSLog(@"鄭明明");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
class_addMethod(self, sel, (IMP)otherEat, "v@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
以上需要注意幾個(gè)地方:
-
otherEat函數(shù)是要被class_addMethod作為參數(shù)的,而class_addMethod是Runtime中的API,所以是基于C的,otherEat函數(shù)應(yīng)該是C語言格式的函數(shù) - class_addMethod方法可謂是核心,那么依次來看他的參數(shù)的含義:
- first:添加到哪個(gè)類
- second:添加哪個(gè)SEL選擇器
- third:IMP函數(shù)指針
- fourth:IMP指針指向的函數(shù)返回值和參數(shù)類型
- v@:代表返回值是void類型,無參數(shù)
- i@:代表返回值是int類型,無參數(shù)
- v@:i@:代表返回值是void類型,參數(shù)是int類型,存在一個(gè)參數(shù)(多參數(shù)依次累加)
- 如果沒有調(diào)用class_addMethod成功添加方法,那么就會到下一個(gè)方法
forwardingTargetForSelector
- (id)forwardingTargetForSelector:(SEL)aSelector {
// return [[Woman alloc]init];
return nil;
}
如果返回了另外一個(gè)對象,那么動態(tài)解析又會重新以另外一個(gè)對象為接受者執(zhí)行,如果返回nil,則又繼續(xù)進(jìn)入到下一個(gè)方法
-
methodSignatureForSelector+forwardInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"]) {
return [NSMethodSignature signatureWithObjCTypes:"@v"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 改變消息接受對象
/*
Woman *temp = [[Woman alloc]init];
[anInvocation invokeWithTarget:temp];
*/
// 改變執(zhí)行的消息
[anInvocation setSelector:@selector(otherEat)];
[anInvocation invokeWithTarget:self];
}
methodSignatureForSelector方法返回一個(gè)返回值以及參數(shù)的封裝值,然后會進(jìn)入到下一個(gè)方法,forwardInvocation,這個(gè)方法的功能可以說是前兩個(gè)方法的結(jié)合,通過操作NSInvocation對象,既可以改變需執(zhí)行的消息,又可以改變消息的接受對象
總結(jié)
Runtime為Objective-C提供了很多可能,了解消息機(jī)制,更加有助于對Objective-C這門語言特性的掌握