1晒骇、objc_msgSend本質(zhì)
在OC中霸褒,方法調(diào)用其實就是轉(zhuǎn)換成objc_msgSend函數(shù)的調(diào)用仓犬。
發(fā)送message只需要指定 對象 和 SEL 侠驯,Runtime的objc_msgSend會根據(jù)信息在對象isa指針指向的Class中尋找該SEL對應的IMP,從而完成方法的調(diào)用镐侯。
MJPerson *peron = [[MJPerson alloc] init];
[peron personTest];
//編譯后.cpp文件:
//((void (*)(id, SEL))(void *)objc_msgSend)((id)peron, sel_registerName("personTest"));
//消息接受者(receiver): peron
//消息名稱: “personTest”
[MJPerson initialize];
//編譯后.cpp文件:
// ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("initialize"));
//消息接受者(receiver): objc_getClass("MJPerson"), 是MJPerson這個類侦讨,而不是實例對象
//消息名稱: initialize
NSLog(@"%p",sel_registerName("personTest"));
NSLog(@"%p",@selector(personTest));
//以上兩句打印的地址是一樣的,這說明方法名一樣苟翻,就是一個東西
//只是編譯器把iOS的@selector()轉(zhuǎn)成了C的sel_registerName()這樣的方法
objc_msgSend的執(zhí)行流程可以分為3大階段:
消息發(fā)送 -> 動態(tài)方法解析 -> 消息轉(zhuǎn)發(fā)
下面韵卤,我們來詳細說明一下:
2、消息發(fā)送
- 檢查receiver是否有效崇猫,有效則開始查找方法沈条;
- 先從receiverClass的cache中查找方法,找得到就跳到對應的函數(shù)去執(zhí)行诅炉,如果cache中找不到就去receiverClass的class_rw_t中去查找蜡歹;
- 如果還是找不到就依次去超類的cache中、class_rw_t中查找涕烧,直到找到NSObject類為止
- 如果還找不到就進入動態(tài)方法解析月而。
3、動態(tài)方法解析
#import "MJPerson.h"
#import <objc/runtime.h>
//method_t的結(jié)構(gòu)如下
//可以利用method_t來定義方法议纯,并且otherMethod->imp父款、otherMethod->types的方式取得imp、types、name
struct method_t {
SEL name;
char *types;
IMP imp;
};
@implementation MJPerson
//方法1:使用結(jié)構(gòu)體method_t來定義方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(personTest)) {
//獲取其他方法
struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//動態(tài)添加方法的實現(xiàn)
class_addMethod(self, sel, otherMethod->imp , otherMethod->types);
//返回YES
return YES;
}
return [super resolveInstanceMethod:sel];
}
//方法2:使用Method來定義方法憨攒,需要通過API取得方法的imp世杀、types、name
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(personTest)) {
//動態(tài)添加方法的實現(xiàn)
Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//動態(tài)添加test方法的實現(xiàn)
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
//方法3:使用C語言函數(shù)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(personTest)) {
//動態(tài)添加方法的實現(xiàn)
class_addMethod(self, sel, (IMP)c_otherTest, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
-(void)otherTest{
NSLog(@"Person otherTest");
}
void c_otherTest(id self,SEL _cmd){
NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
@end
上面代碼中的resolveInstanceMethod是用來動態(tài)解析實例方法的肝集,
類方法是用resolveClassMethod來動態(tài)解析的玫坛。
對于類方法,基本和實例方法一樣包晰,只是需要實現(xiàn)resolveClassMethod方法;另外在class_addMethod
中需要用object_getClass(self)
取得類對象炕吸,對類對象進行添加方法操作伐憾;
struct method_t {
SEL name;
char *types;
IMP imp;
};
//方法1:使用結(jié)構(gòu)體method_t來定義方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(PersonClassTest)) {
//獲取其他方法
struct method_t *otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//因為是MJPerosn這個類去調(diào)用,要用object_getClass(self)
class_addMethod(object_getClass(self), sel, otherMethod->imp , otherMethod->types);
//返回YES
return YES;
}
return [super resolveClassMethod:sel];
}
//方法2:使用Method來定義方法赫模,需要通過API取得方法的imp树肃、types、name
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(PersonClassTest)) {
Method otherMethod = class_getInstanceMethod(self, @selector(otherTest));
//因為是MJPerosn這個類去調(diào)用瀑罗,要用object_getClass(self)
class_addMethod(object_getClass(self), sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
return YES;
}
return [super resolveInstanceMethod:sel];
}
//方法3:使用C語言函數(shù)
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(PersonClassTest)) {
//因為是MJPerosn這個類去調(diào)用胸嘴,要用object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_otherTest, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
-(void)otherTest{
NSLog(@"Person cl otherTest");
}
void c_otherTest(id self,SEL _cmd){
NSLog(@"C Person otherTest %@ -- %@",self,NSStringFromSelector(_cmd));
}
- 如果事先沒有動態(tài)解析,那便會通過resolveInstanceMethod / resolveClassMethod方法斩祭,對receiver動態(tài)添加方法劣像,然后再進入消息發(fā)送;
- 如果之前已經(jīng)有動態(tài)方法解析了摧玫,那進入消息轉(zhuǎn)發(fā)耳奕。
4、消息轉(zhuǎn)發(fā)
forwardingTargetForSelector方法
//程序中調(diào)用personTest
MJPerson *person = [[MJPerson alloc] init];
[person personTest];
//在person.m文件中實現(xiàn):
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(personTest)) {
//返回MJCat诬像,要求在MJCat中實現(xiàn)personTest方法
//就相當于調(diào)用了objc_msgSend([[MJCat alloc] init],aSelector);
return [[MJCat alloc] init];
//通過建立分類屋群,在NSObject分類中實現(xiàn)personTest方法,也可以成功實現(xiàn)消息轉(zhuǎn)發(fā)
//return [[NSObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
消息轉(zhuǎn)發(fā)坏挠,如果forwardingTargetForSelector這個方法沒有實現(xiàn) 或者 這個方法return nil的話芍躏。這種情況下,會調(diào)用methodSignatureForSelector方法
#pragma mark -無參的無返回值的可以用v@: 不一定非要寫數(shù)字
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(personTest)) {
//返回方法簽名:包含返回值類型降狠、參數(shù)類型
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//也可以使用下面這句代替对竣,這樣就不用自己管Type Encodings了
//return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封裝了一個方法調(diào)用:包含方法調(diào)用者、方法名喊熟、方法參數(shù)
// anInvocation.target ---> 方法調(diào)用者
// anInvocation.selector ---> 方法名字
// [anInvocation getArgument:NULL atIndex:0] ---> 方法參數(shù)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"可以在這個方法中實現(xiàn)你想做的操作");
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}
#pragma mark -對于有參柏肪,有返回值的,anInvocation中也有更多的功能
//ageTest:方法是傳入int類型的age芥牌,然后乘以2再返回
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(ageTest:)) {
return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
//也可以使用下面這句代替烦味,這樣就不用自己管Type Encodings了
//return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret]; //getReturnValue是獲取方法返回值,anInvocation中還有其他更多的數(shù)據(jù)
NSLog(@"打印結(jié)果 %d",ret);
}
對于類方法來說,消息轉(zhuǎn)發(fā)時谬俄,需要有點改變:就是要將調(diào)用的方法改成+開頭柏靶,也就是類方法;另外消息接受者為類溃论,而不是實例對象屎蜓。
#pragma mark - - (id)forwardingTargetForSelector:(SEL)aSelector對應的
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(personTest)) {
//因為是調(diào)用類方法,所以這里要注意一下
return [MJCat class];
}
return [super forwardingTargetForSelector:aSelector];
}
當沒有實現(xiàn)forwardingTargetForSelector方法或者這個方法返回nil時钥勋,methodSignatureForSelector方法炬转,注意都是類方法,帶+號的算灸。
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(PersonClassTest)){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
//這里調(diào)用[MJCat class]這個類
[anInvocation invokeWithTarget:[MJCat class]];
}
5扼劈、總結(jié)
-
<1> 介紹一下OC的消息機制
OC中的方法調(diào)用都是轉(zhuǎn)成obje_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
objc_msgSend底層有三大階段:
(1)消息發(fā)送(在當前類菲驴、父類中查找方法)
(2)動態(tài)方法解析
(3)消息轉(zhuǎn)發(fā)
這三大階段的具體流程可以根據(jù)流程圖解釋荐吵。