消息转发

579 阅读3分钟

objc_msgSend查找流程:

objc_msgSend首先通过汇编快速查找方法缓存,如果找到,直接将方法缓存起来然后进行调用就可以了,如果查找不到就跳到CheckMiss,然后走慢速查找流程。步骤如下:

  • 获取传入对象所属的类
  • 获取该类的方法缓存表
  • 使用传入的sel选择器在缓存中查询
  • 如果缓存中不存在,则开始慢速查找流程
  • 慢速查找流程,找到后,跳转到IMP映射位置的方法 当经过了快速查找和慢速查找之后,没有找到时又会进行哪些流程呢?

动态方法解析

Xnip2022-02-07_17-30-09.jpg

#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;
}

总结

实现消息的转发主要有上面的三种方式,流程如下: 491644291687_.pic.jpg

v@:

在上面的代码中看到很多v@:的符号,我们解释一下这个,OC中的方法默认有两个参数:self和_cmd。self指向对象本身,_cmd指向方法本身。如下:

//这个方法实际上有两个参数:self和_cmd
-(NSString *)name;
//这个方法实际上有三个参数:self _cmd 和name
-(void)setName:(NSString*)name;

被指定为动态实现的方法的参数类型有如下的要求:

  • 第一个参数类型必须是id(self)
  • 第二个参数类型必须是SEL(_cmd)
  • 从第三个参数起,可以按照原方法的参数类型定义 苹果对每个类型进行了规定 Xnip2022-02-08_10-51-23.jpg 例如下面方法:
//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 @"";
}

Xnip2022-02-08_11-17-29.jpg