objc_msgSend查找流程:
objc_msgSend首先通过汇编快速查找方法缓存,如果找到,直接将方法缓存起来然后进行调用就可以了,如果查找不到就跳到CheckMiss,然后走慢速查找流程。步骤如下:
- 获取传入对象所属的类
- 获取该类的方法缓存表
- 使用传入的sel选择器在缓存中查询
- 如果缓存中不存在,则开始慢速查找流程
- 慢速查找流程,找到后,跳转到IMP映射位置的方法 当经过了快速查找和慢速查找之后,没有找到时又会进行哪些流程呢?
动态方法解析
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Person : NSObject
-(void)run;
+(void)run2;
@end
@implementation Person
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"%s", __func__);
}
void dynamicMethodIMP2(id self, SEL _cmd) {
NSLog(@"%s", __func__);
}
//实例方法动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(run)) {
return class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
}
return [super resolveInstanceMethod:sel];
}
//类方法动态解析
+(BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(run2)) {
return class_addMethod(objc_getMetaClass("Person"), sel, (IMP)dynamicMethodIMP2, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc]init];
[p run];
[Person run2];
}
return 0;
}
当 run方法找不到的时候,我们会自动走到+ (BOOL)resolveInstanceMethod:(SEL)sel,
然后动态的将dynamicMethodIMP 函数添加给 Person,从而避免了程序崩溃,这就是方法的动态解析
快速转发
拿上面实例方法举例来讲,当我们当前实例对象p没有实现run方法的时候,我们可以将这个方法转发给其他对象。
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Student : NSObject
-(void)run;
@end
@implementation Student
-(void)run{
NSLog(@"run");
}
@end
@interface Person : NSObject
-(void)run;
@end
@implementation Person
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
//自己没有实现,转发给别人
return [Student new];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc]init];
[p run];
}
return 0;
}
慢速转发
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface Student : NSObject
-(void)run;
@end
@implementation Student
-(void)run{
NSLog(@"Student run");
}
@end
@interface Person : NSObject
-(void)run;
@end
@implementation Person
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(run)) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s",__func__);
SEL aSelector = [anInvocation selector];
Student *forward = [Student new];
if ([forward respondsToSelector:aSelector]) {
[anInvocation invokeWithTarget:forward];
}else {
[super forwardInvocation:anInvocation];
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc]init];
[p run];
}
return 0;
}
总结
实现消息的转发主要有上面的三种方式,流程如下:
v@:
在上面的代码中看到很多v@:的符号,我们解释一下这个,OC中的方法默认有两个参数:self和_cmd。self指向对象本身,_cmd指向方法本身。如下:
//这个方法实际上有两个参数:self和_cmd
-(NSString *)name;
//这个方法实际上有三个参数:self _cmd 和name
-(void)setName:(NSString*)name;
被指定为动态实现的方法的参数类型有如下的要求:
- 第一个参数类型必须是id(self)
- 第二个参数类型必须是SEL(_cmd)
- 从第三个参数起,可以按照原方法的参数类型定义
苹果对每个类型进行了规定
例如下面方法:
//age为int类型,那么第三个参数就是i v@:i
-(void)setAge:(int)age;
//name为NSString类型,那么第三个参数就是@ v@:@
-(void)setName:(NSString*)name;
//返回值类型NSString 参数NSString @@:@
-(NSString *)getName:(NSString *)val;
如果还是不能正确写出,我们可以借助程序
-(NSString *)getName:(NSString *)val{
Method method = class_getInstanceMethod(self.class, @selector(getName:));
const char *des = method_getTypeEncoding(method);
NSString *desStr = [NSString stringWithCString:des encoding:NSUTF8StringEncoding];
NSLog(@"%@",desStr);
return @"";
}