Objective-C是一門(mén)動(dòng)態(tài)語(yǔ)言,類型的判斷、類的成員變量、方法的內(nèi)存地址都是在程序的運(yùn)行階段才最終確定,并且還能動(dòng)態(tài)的添加成員變量和方法。這意味著即使調(diào)用對(duì)象一個(gè)沒(méi)有實(shí)現(xiàn)的方法,編譯也能通過(guò),甚至一個(gè)對(duì)象它是什么類型并不是表面我們所看到的那樣,只有運(yùn)行之后才能決定其真正的類型。因此OC具有多態(tài)、動(dòng)態(tài)類型、動(dòng)態(tài)綁定特性。
正是因?yàn)槠?code>動(dòng)態(tài)特性,在OC中調(diào)用方法并不是一個(gè)簡(jiǎn)單的函數(shù)調(diào)用過(guò)程,它并不直接調(diào)用方法,而是運(yùn)用Runtime機(jī)制實(shí)現(xiàn)函數(shù)調(diào)用,稱之為消息發(fā)送。
Runtime
Runime運(yùn)行時(shí),是OC中一套底層的C語(yǔ)言API,底層都是基于它來(lái)實(shí)現(xiàn)的。在運(yùn)行時(shí),我們所編寫(xiě)的代碼會(huì)轉(zhuǎn)換為C語(yǔ)言運(yùn)行。具體內(nèi)容可以參考:runtime概述
在OC中發(fā)送一條消息:[receiver message],
會(huì)轉(zhuǎn)為調(diào)用底層函數(shù):id objc_msgSend(id self, SEL op, ...)
讓我們先看看這個(gè)函數(shù)的參數(shù)和返回值
id在OC中表示任意類型的對(duì)象,其結(jié)構(gòu)如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
顯然id是一個(gè)objc_objec結(jié)構(gòu)體類型指針,其內(nèi)部有一個(gè)Class類型的isa指針,指向?qū)?yīng)的類。
SEL是對(duì)方法的包裝,返回對(duì)應(yīng)的編號(hào)
struct objc_class {
Class _Nonnull isa 指向自身對(duì)應(yīng)的類;
#if !__OBJC2__
Class _Nullable super_class 指向父類
const char * _Nonnull name
long version
long info
long instance_size
struct objc_ivar_list * _Nullable ivars
struct objc_method_list * _Nullable * _Nullable methodLists 類中的方法信息列表
struct objc_cache * _Nonnull cache 調(diào)用過(guò)的方法信息列表
struct objc_protocol_list * _Nullable protocols
#endif
}
上面是class的結(jié)構(gòu)
消息發(fā)送過(guò)程一 --- 函數(shù)在類中實(shí)現(xiàn)
當(dāng)發(fā)送一條消息時(shí),object_getClass(id obj)通過(guò)id的isa指針找到對(duì)象的對(duì)應(yīng)的類class,在class中先去cache中 通過(guò)SEL查找對(duì)應(yīng)函數(shù)method(cache中method列表是以SEL為key通過(guò)hash表來(lái)存儲(chǔ)的,這樣能提高函數(shù)查找速度),若 cache中未找到,方法列表methodList中查找,若能找到,則將method加 入到cache中,以方便下次查找。否則到對(duì)應(yīng)的super_class中查找,如此循環(huán)直到NSObject。
我們具體看OC如何用runtime實(shí)現(xiàn)函數(shù)調(diào)用:
自定義Person對(duì)象
@interface Person : NSObject
+ (void)eat;
- (void)eat;
- (void)run:(int)age;
@end
@implementation Person
+ (void)eat
{
NSLog(@"類方法-吃東西");
}
- (void)eat
{
NSLog(@"實(shí)例方法-吃東西");
}
- (void)run:(int)age
{
NSLog(@"跑了%d米",age);
}
@end
-------------------------UIViewController-----------------------------
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
objc_msgSend(person, @selector(eat));
objc_msgSend(person, @selector(run:),10);
Class personClass = [Person class];
objc_msgSend(personClass, @selector(eat));
}
-------------------------console-----------------------------
[1373:101045] 實(shí)例方法-吃東西
[1373:101045] 跑了10米
[1373:101045] 類方法-吃東西
以上就是發(fā)送消息調(diào)用類中實(shí)現(xiàn)的方法的過(guò)程。
消息發(fā)送過(guò)程二 --- 函數(shù)在類中未實(shí)現(xiàn),動(dòng)態(tài)解析方法
當(dāng)向receiver發(fā)送message時(shí),若SEL對(duì)應(yīng)的函數(shù)未在類或者類的父類中實(shí)現(xiàn),程序并不會(huì)立即crash,而是對(duì)message進(jìn)一步轉(zhuǎn)發(fā),調(diào)用_objc_msgForward(id receiver, SEL sel, ...),具體表現(xiàn)在類中對(duì)應(yīng)的方法為:
1.+ resolveInstanceMethod:(SEL)sel // 對(duì)應(yīng)實(shí)例方法
+ resolveClassMethod:(SEL)sel // 對(duì)應(yīng)類方法
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (void)forwardInvocation:(NSInvocation *)anInvocation
注意:以上方法優(yōu)先級(jí)依次遞減,高優(yōu)先級(jí)方法消息轉(zhuǎn)發(fā)成功不會(huì)再執(zhí)行低優(yōu)先級(jí)方法
現(xiàn)在為Person實(shí)例對(duì)象發(fā)送一條@selector(wy_sleep:)消息:
[person performSelector:@selector(wy_sleep:) withObject:@100];
因?yàn)?code>person未實(shí)現(xiàn)@selector(wy_sleep:),消息轉(zhuǎn)發(fā)轉(zhuǎn)而調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel:,判斷類是在運(yùn)行時(shí)動(dòng)態(tài)的添加方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(wy_sleep:)) {
/*
cls:給哪個(gè)類添加方法
SEL:添加方法的方法編號(hào)是什么
IMP:方法實(shí)現(xiàn),函數(shù)入口,函數(shù)名
types:方法類型
最后一個(gè)參數(shù)請(qǐng)查看文檔Help - API reference - runtime
*/
class_addMethod([self class], sel, (IMP)wy_sleep, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void wy_sleep(id self, SEL _cmd, id param1){
NSLog(@"調(diào)用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);
}
-------------------------console-----------------------------
[1553:136714] 調(diào)用wy_sleep <Person: 0x60000003ed40> wy_sleep: 100
其中class_addMethod(Class cls, SEL name, IMP imp, const char *types)為類添加新的方法實(shí)現(xiàn)。
消息發(fā)送過(guò)程三 --- 函數(shù)在類中未實(shí)現(xiàn)且無(wú)動(dòng)態(tài)解析,快速轉(zhuǎn)發(fā)消息
當(dāng)調(diào)用的方法沒(méi)有在類中實(shí)現(xiàn),且沒(méi)有動(dòng)態(tài)添加時(shí),則消息繼續(xù)轉(zhuǎn)發(fā),判斷類中是否預(yù)備了備用的消息接受者
- (id)forwardingTargetForSelector:(SEL)aSelector
簡(jiǎn)單、快速,但只能只能指定一個(gè)轉(zhuǎn)發(fā)對(duì)象,無(wú)法對(duì)消息進(jìn)行操作
定義對(duì)象A、B
-------------------------A-----------------------------
@interface A : NSObject
- (void)prinAName;
@end
@implementation A
- (void)prinAName
{
NSLog(@"Hello World! My name is A");
}
@end
-------------------------B-----------------------------
@interface B : NSObject
- (void)prinBName;
@end
@implementation B
- (void)prinBName
{
NSLog(@"Hello World! My name is B");
}
@end
實(shí)現(xiàn)Person中的的快速消息轉(zhuǎn)發(fā):
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (class_respondsToSelector([A class], aSelector)) {
return [[A alloc] init];
}
B *b = [[B alloc] init];
if ([b respondsToSelector:aSelector]) {
return b;
}
return [super forwardingTargetForSelector:aSelector];
}
向person實(shí)例發(fā)送相應(yīng)的消息:
[person performSelector:@selector(prinAName)];
[person performSelector:@selector(prinBName)];
輸出結(jié)果:
[1962:235819] Hello World! My name is A
[1962:235819] Hello World! My name is B
消息發(fā)送過(guò)程四 --- 函數(shù)在類中未實(shí)現(xiàn)且無(wú)動(dòng)態(tài)解析,完整的消息轉(zhuǎn)發(fā)
除了設(shè)置備用消息接受者,快速轉(zhuǎn)發(fā)消息外,還能將完整的消息轉(zhuǎn)發(fā)給實(shí)現(xiàn)的對(duì)象
-(void)forwardInvocation:(NSInvocation *)anInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [A instanceMethodSignatureForSelector:aSelector];
if (methodSignature == nil) {
methodSignature = [B instanceMethodSignatureForSelector:aSelector];
}
if (methodSignature == nil) {
methodSignature = [super methodSignatureForSelector:aSelector];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
A *a = [[A alloc] init];
B *b = [[B alloc] init];
if ([a respondsToSelector:selector]) {
[anInvocation invokeWithTarget:a];
} else if ([b respondsToSelector:selector]) {
[anInvocation invokeWithTarget:b];
} else {
[super forwardInvocation:anInvocation];
}
}
消息發(fā)送過(guò)程五 --- Crash
當(dāng)前面消息發(fā)送的4個(gè)過(guò)程都沒(méi)有實(shí)現(xiàn)時(shí),程序調(diào)用- (void)doesNotRecognizeSelector:(SEL)aSelector崩潰
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"無(wú)法實(shí)現(xiàn)方法,crash");
}
總結(jié):
OC中函數(shù)調(diào)用通過(guò)發(fā)送消息實(shí)現(xiàn),分為五個(gè)步驟,如下:
