經(jīng)過(guò)上面的學(xué)習(xí)我們知道,當(dāng)調(diào)用方法[person test],就是給person對(duì)象發(fā)送test消息,然后通過(guò)isa->superclass-> superclass......尋找類對(duì)象,找到類對(duì)象之后,還會(huì)先通過(guò)@selector(test)&_mask生成索引,通過(guò)索引直接在cache里的散列表里面取方法,如果cache里面沒(méi)有方法,再遍歷methods數(shù)組......
OC的方法調(diào)用:消息機(jī)制,就是給方法調(diào)用者發(fā)送消息,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用。
比如:
[person personTest];
轉(zhuǎn)成C++代碼就是:
objc_msgSend(person, sel_registerName("personTest"));
以前我們講過(guò)sel_registerName("personTest")和@selector(personTest)返回的都是SEL,而且打印發(fā)現(xiàn)他們的地址的確也相同,所以上面的代碼也可以寫成:
objc_msgSend(person, @selector(personTest));
消息接收者:person
消息名稱:personTest
意思就是給person對(duì)象發(fā)送personTest消息。
同理,類方法調(diào)用:
[MJPerson initialize];
轉(zhuǎn)成C++代碼:
(objc_getClass("MJPerson"), sel_registerName("initialize"));
也可以寫成:
objc_msgSend([MJPerson class], @selector(initialize));
意思就是給MJPerson類對(duì)象發(fā)送initialize消息。
一. objc_msgSend的執(zhí)行流程
objc_msgSend的執(zhí)行流程可以分為3大階段:
- 消息發(fā)送:就是根據(jù)isa、superclass尋找方法
- 動(dòng)態(tài)方法解析:允許開(kāi)發(fā)者動(dòng)態(tài)創(chuàng)建新的方法
- 消息轉(zhuǎn)發(fā):轉(zhuǎn)發(fā)給另外一個(gè)對(duì)象調(diào)用這個(gè)方法
objc_msgSend內(nèi)部的這三個(gè)階段經(jīng)歷完還找不到方法就報(bào)錯(cuò):unrecognized selector sent to instance/class。
由于源碼解析比較麻煩,我放到objc_msgSend源碼解析這篇文章里面了,強(qiáng)烈建議先看一下,這樣就很容易理解下面的流程,下面直接說(shuō)結(jié)論。
一. 消息發(fā)送
二. 動(dòng)態(tài)方法解析
下面通過(guò)動(dòng)態(tài)添加方法來(lái)驗(yàn)證階段二,動(dòng)態(tài)方法解析。
1. 實(shí)例對(duì)象的動(dòng)態(tài)方法解析
先創(chuàng)建MJPerson對(duì)象,只聲明方法,不實(shí)現(xiàn)方法:
調(diào)用代碼:
MJPerson *person = [[MJPerson alloc] init];
[person test];
[MJPerson test];
發(fā)現(xiàn)會(huì)報(bào)錯(cuò):
unrecognized selector sent to instance 0x10074a610
unrecognized selector sent to class 0x100001118
下面動(dòng)態(tài)添加實(shí)例方法,動(dòng)態(tài)添加實(shí)例方法需要我們實(shí)現(xiàn)resolveInstanceMethod方法。
在MJPerson.m添加如下代碼:
- (void)other
{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 獲取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
打印:
-[MJPerson other]
可以發(fā)現(xiàn)方法添加成功。調(diào)用test,但是test沒(méi)找到,會(huì)進(jìn)入resolveInstanceMethod里面動(dòng)態(tài)添加方法,方法添加完成會(huì)重新走消息發(fā)送流程,然后就找到了other,調(diào)用other,然后打印-[MJPerson other]。
補(bǔ)充:Method就是method_t
可能你不知道Method是什么,其實(shí)Method就是在iOS-底層-Runtime2里面講過(guò)的method_t,method_t的結(jié)構(gòu)體是這樣的:
struct method_t {
SEL sel;
char *types;
IMP imp;
};
所以,上面的代碼就能修改為:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 獲取其他方法
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
//Method幾乎等價(jià)于以前講的method_t,可打印驗(yàn)證
NSLog(@"%s, %s, %p",method->sel,method->types,method->imp);
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel, method->imp, method->types);
// 返回YES代表有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
執(zhí)行代碼,打印結(jié)果如下:
other, v16@0:8, 0x100000dd0
-[MJPerson other]
可以發(fā)現(xiàn),函數(shù)名、函數(shù)編碼(參數(shù)、返回值)、函數(shù)地址都打印出來(lái)了,other方法也調(diào)用了,說(shuō)明動(dòng)態(tài)方法添加成功,Method和method_t沒(méi)啥區(qū)別。
2. 類對(duì)象的動(dòng)態(tài)方法解析
那么如果調(diào)用的是類方法呢?
需要在resolveClassMethod方法里面,動(dòng)態(tài)添加類方法。
MJPerson *person = [[MJPerson alloc] init];
//[person test];
[MJPerson test];
動(dòng)態(tài)添加類方法:
//動(dòng)態(tài)添加c語(yǔ)言函數(shù)的實(shí)現(xiàn)
void c_other(id self, SEL _cmd)//這也驗(yàn)證了OC的方法都有兩個(gè)隱式參數(shù)(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
//動(dòng)態(tài)添加類方法
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一個(gè)參數(shù)是object_getClass(self)
//c語(yǔ)言的函數(shù)地址就是函數(shù)名
//object_getClass(self)獲取元類對(duì)象
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
注意:動(dòng)態(tài)添加實(shí)例方法傳入到class_addMethod函數(shù)中的是當(dāng)前類對(duì)象self,動(dòng)態(tài)添加類方法傳入到class_addMethod函數(shù)中的是元類對(duì)象object_getClass(self)。
上面代碼運(yùn)行后,打印:
c_other - MJPerson - test
可以發(fā)現(xiàn)添加成功,上面不但驗(yàn)證了類方法可以添加成功,而且驗(yàn)證了,還可以使用c語(yǔ)言函數(shù)作為他們的實(shí)現(xiàn)。
上面代碼Demo地址:動(dòng)態(tài)方法解析
三. 消息轉(zhuǎn)發(fā)
關(guān)于消息轉(zhuǎn)發(fā)的邏輯如上圖,下面進(jìn)行驗(yàn)證。
1. 實(shí)例對(duì)象的消息轉(zhuǎn)發(fā)
① forwardingTargetForSelector返回對(duì)象
在MJPerson.m里面實(shí)現(xiàn)如下代碼:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
return [[MJCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
執(zhí)行:
MJPerson *person = [[MJPerson alloc] init];
[person test];
打印:
-[MJCat test]
可以發(fā)現(xiàn),person對(duì)象沒(méi)實(shí)現(xiàn)test方法,會(huì)調(diào)用MJCat的test方法。
那如果forwardingTargetForSelector返回值為空呢?
② methodSignatureForSelector返回方法簽名
//方法簽名:返回值類型、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
/*
NSInvocation封裝了一個(gè)方法調(diào)用,包括:方法調(diào)用者、方法名、方法參數(shù)
anInvocation.target 方法調(diào)用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0] 獲取參數(shù)
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
//上面下面都可以了
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
上面的代碼,我們?cè)诜祷胤椒ê灻螅補(bǔ)nInvocation.target給修改為MJCat,所以最后還是會(huì)調(diào)用MJCat的test方法。
上面的代碼,如果僅僅是想要調(diào)用MJCat的test方法,在forwardingTargetForSelector里面修改其實(shí)更簡(jiǎn)單。
在forwardInvocation做更多操作:
[person test]執(zhí)行的代碼其實(shí)就是forwardInvocation方法里面執(zhí)行的代碼,所以我們可以在forwardInvocation方法里面做任何操作,比如只打印,獲取參數(shù),獲取返回值。
首先,MJPerson只聲明不實(shí)現(xiàn)test方法,MJCat實(shí)現(xiàn)test方法,如下
@implementation MJCat
- (int)test:(int)age
{
return age * 2;
}
@end
在MJPerson.m里面實(shí)現(xiàn)如下代碼:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test:)) {
//直接返回方法簽名
//return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
//方法簽名省略數(shù)字
return [NSMethodSignature signatureWithObjCTypes:"i@:I"];
//拿到MJCat的test方法簽名來(lái)用
//return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//參數(shù)順序:receiver(也就是self)、selector、other arguments
int age;
[anInvocation getArgument:&age atIndex:2];
NSLog(@"%d", age + 10);
//打印:15 + 10 = 25;
/*
anInvocation.target 是 [[MJCat alloc] init]
anInvocation.selector 是 test:
anInvocation的參數(shù):15
*/
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
//打印:15 * 2 = 30;
}
關(guān)于返回方法簽名、獲取參數(shù)、獲取返回值可看注釋。
2. 類對(duì)象的消息轉(zhuǎn)發(fā)
剛才講的是實(shí)例對(duì)象的消息轉(zhuǎn)發(fā),如果是類對(duì)象需要實(shí)現(xiàn)+號(hào)開(kāi)頭的方法,如下:
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) return [MJCat class];
//+[MJCat test]
return [super forwardingTargetForSelector:aSelector];
}
返回MJCat類對(duì)象,當(dāng)調(diào)用[MJPerson test],會(huì)打印:+[MJCat test],調(diào)用了MJCat的test方法。
上面的代碼如果返回的是實(shí)例對(duì)象呢?如下:
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
// objc_msgSend([[MJCat alloc] init], @selector(test))
// [[[MJCat alloc] init] test]
if (aSelector == @selector(test)) return [[MJCat alloc] init];
//-[MJCat test]
return [super forwardingTargetForSelector:aSelector];
}
可以發(fā)現(xiàn),返回MJCat實(shí)例對(duì)象,當(dāng)調(diào)用[MJPerson test],會(huì)打印:-[MJCat test],調(diào)用了MJCat實(shí)例對(duì)象的對(duì)象方法,為什么會(huì)這樣呢?
通過(guò)源碼分析可知,forwardingTargetForSelector方法內(nèi)部就是給返回的對(duì)象發(fā)送test消息,所以返回的是實(shí)例對(duì)象就是給實(shí)例對(duì)象發(fā)送消息,自然就是如下:
objc_msgSend([[MJCat alloc] init], @selector(test))
同樣,如果上面方法返回為空,也會(huì)走以下代碼:
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"1123");
}
打印:
1123
補(bǔ)充:@synthesize、@dynamic
首先在MJPerson.h寫如下代碼:
@property (assign, nonatomic) int age;
我們都知道,編譯器會(huì)自動(dòng)生成_age成員變量、setter和getter的聲明、setter和getter的實(shí)現(xiàn)。
但是在很久以前,Xcode還沒(méi)這么智能的時(shí)候,如果只寫上面那句還不行,因?yàn)锧property只負(fù)責(zé)生成settetr和getter方法的聲明。還要在.m文件中使用@synthesize。
@synthesize age = _age, height = _height;
這時(shí)候編譯器才會(huì)生成_age成員變量、setter和getter的實(shí)現(xiàn)。
如果使用@dynamic就是提醒編譯器不要自動(dòng)生成成員變量,不要自動(dòng)生成setter和getter的實(shí)現(xiàn),等到運(yùn)行時(shí)再添加方法實(shí)現(xiàn)。
MJPerson.m代碼如下:
@dynamic age;
void setAge(id self, SEL _cmd, int age)
{
NSLog(@"age is %d", age);
}
int age(id self, SEL _cmd)
{
return 120;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(setAge:)) {
class_addMethod(self, sel, (IMP)setAge, "v@:i");
return YES;
} else if (sel == @selector(age)) {
class_addMethod(self, sel, (IMP)age, "i@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
執(zhí)行代碼:
MJPerson *person = [[MJPerson alloc] init];
person.age = 20;
NSLog(@"%d", person.age);
打印:
age is 20
120
可以發(fā)現(xiàn),使用@dynamic就沒(méi)有生成_age成員變量、setter和getter的實(shí)現(xiàn),這時(shí)候我們自己通過(guò)Runtime動(dòng)態(tài)添加了setter和getter的實(shí)現(xiàn)才實(shí)現(xiàn)了如上打印。
總結(jié):@synthesize自動(dòng)生成_age成員變量、setter和getter的實(shí)現(xiàn),@dynamic不自動(dòng)生成_age成員變量、setter和getter的實(shí)現(xiàn),正好是反過(guò)來(lái)的。
Demo地址:消息轉(zhuǎn)發(fā)
面試題:
- 講一下OC的消息機(jī)制
OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)。
objc_msgSend底層有3大階段:消息發(fā)送(當(dāng)前類、父類中查找)、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)。
- 說(shuō)一下消息轉(zhuǎn)發(fā)流程
當(dāng)消息發(fā)送和動(dòng)態(tài)方法解析都沒(méi)找到方法就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段
① 首先會(huì)調(diào)用+或-開(kāi)頭的forwardingTargetForSelector方法,如果這個(gè)方法返回值不為空,就給返回值發(fā)送SEL消息:objc_msgSend(返回值, SEL)。
② 如果這個(gè)方法的返回值為空,就會(huì)調(diào)用+或-開(kāi)頭的methodSignatureForSelector方法,如果這個(gè)方法返回值不為空,就會(huì)再調(diào)用+或-開(kāi)頭的forwardInvocation方法,我們可以在forwardInvocation里面方法做任何我們想做的事。
③ 如果這個(gè)方法的返回值為空,就會(huì)調(diào)用doesNotRecognizeSelector,報(bào)錯(cuò)unrecognized selector sent to instance/class。