iOS底层原理

iOS 从runtime理解消息转发原理和用途

2019-06-24  本文已影响0人  风雨彩虹_123

前言

从一个对象收到一个无法响应的方法到崩溃之间发生了什么?runtime在底层做了哪些操作?
OC对象在发送消息的时候会在该类的缓存列表中查找方法,如果找到直接调用相关方法的实现,如果没有会到类的struct objc_method_list列表中去搜索,如果找到则直接调用相关方法的实现,如果没有找到就会通过super_class指针沿着继承树向上去搜索。如果到了根部还没有找到,这是runtime大神就会帮我们做些事情,防止我们的引用崩溃。

我们在开发中经常会遇到这样的报错,当我们调用一个无响应的方法

    self.testVC = [[TestViewController alloc]init];
    [self.testVC performSelector:@selector(drink)];
    [self.testVC drink];

在控制台会打印出如下错误提示


屏幕快照 2019-06-21 14.47.50.png

上面两种方法在执行过程中有什么区别呢?


屏幕快照 2019-06-21 14.56.21.png
官方文档说的很清楚,执行的效果其实和发送消息是等价的,但是 performSelector可以发送在运行时没有确定的消息。当我们为类动态添加方法是,必须用此种方法进行调用,为了避免运行时出现错误,在使用performSelector之前一定要使用如下的检查方法来进行判断。
- (BOOL)respondsToSelector:(SEL)aSelector;

消息转发

    //方法的调用
   TestViewController  *testVC = [[TestViewController alloc]init];
    /******类方法调用******/
    [TestViewController performSelector:@selector(testClassFunction)];
    /******对象方法调用******/
    [testVC performSelector:@selector(testFunction)];//此处必须用performSelector这个方法
/**
 类:如果是类方法的调用,首先会触发该类方法
 @param sel 传递进入的方法
 @return 如果YES则能接受消息 NO不能接受消息 进入第二步
 */
+(BOOL)resolveClassMethod:(SEL)sel{
    
    if ([NSStringFromSelector(sel) isEqualToString:@"testClassFunction"]) {
        /**
         对类进行添加类方法 需要讲方法添加进入元类内
         */
        [self addMethodWithClass:object_getClass([self class]) withMethodSel:sel withImpMethodSel:@selector(addClassDynamicMethod)];
        return YES;
    }
    return [super resolveClassMethod:sel];
}
/**
 对象:在接受到无法解读的消息的时候 首先会调用所属类的类方法
 @param sel 传递进入的方法
 @return 如果YES则能接受消息 NO不能接受消息 进入第二步
 */
+(BOOL)resolveInstanceMethod:(SEL)sel{
    //判断是否为外部调用的方法
    if ([NSStringFromSelector(sel) isEqualToString:@"testFunction"]) {
        /**
         对类进行对象方法 需要把方法添加进入类内
         */
        [self addMethodWithClass:[self class] withMethodSel:sel withImpMethodSel:@selector(addDynamicMethod)];
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
+(void)addMethodWithClass:(Class)class withMethodSel:(SEL)methodSel withImpMethodSel:(SEL)impMethodSel{
    //获取实现的方法内容
    Method funtionRealMethod = class_getInstanceMethod(class, impMethodSel);
    //实现方法的IMP
    IMP methodIMP = method_getImplementation(funtionRealMethod);
    //实现方法的类别 返回类型 携带参数 等
    const char * types = method_getTypeEncoding(funtionRealMethod);
    //对目标添加方法
    class_addMethod(class, methodSel, methodIMP, types);
}
-(void)addDynamicMethod{
    NSLog(@"动态添加方法");
}
+(void)addClassDynamicMethod{
    NSLog(@"动态添加类方法");
}

类方法是存储在元类里面,object_getClass([self class])是获取元类对象。动态给类添加方法主要是class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 这个方法。第一个参数是需要添加方法的类,第二个参数是一个selector,也就是实例方法的名字,第三个参数是一个IMP类型的变量也就是函数实现,需要传入一个C函数,这个函数至少有两个参数,一个是id self一个是SEL _cmd,第四个参数是函数类型。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    class_addMethod([self class], sel, (IMP)addMethod, "v@:@");
    return YES;
}
id addMethod(id self, SEL _cmd) {
    NSLog(@"WOCrashProtector: unrecognized selector: %@", NSStringFromSelector(_cmd));
    return 0;
}

在NSObject的分类中重写下列的方法。

- (id)forwardingTargetForSelector:(SEL)aSelector {
        IMP impOfNSObject = class_getMethodImplementation([NSObject class], @selector(forwardInvocation:));
        IMP imp = class_getMethodImplementation([self class], @selector(forwardInvocation:));
        //此处是判断类是否已实现调用
        if (imp != impOfNSObject) {
            //NSLog(@"class has implemented invocation");
            return nil;
        }
    //创建备用接受者去处理消息
    WOUnrecognizedSelectorSolveObject *solveObject = [WOUnrecognizedSelectorSolveObject new];
    solveObject.objc = self;
//    [solveObject runTests];
    return solveObject;
}

在上面方法返回WOUnrecognizedSelectorSolveObject,并且在WOUnrecognizedSelectorSolveObject中动态添加了可以响应的方法,所以就不会再崩溃了。对于本身类无法响应的方法可以找一个对象来完成。

//第三步的消息转发机制本质上跟第二步是一样的都是切换接受消息的对象
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //如果返回为nil则进行签名
    if ([super methodSignatureForSelector:aSelector]==nil) {
           BackupTestMessage * backUp = [BackupTestMessage new];
     NSMethodSignature * sign = [backUp methodSignatureForSelector:aSelector];
        return sign;
    }
    return [super methodSignatureForSelector:aSelector];
}

//上方方法如果调用返回有签名 则进入消息转发最后一步
//JSPatch 就是使用了该方法 来做了动态热更新
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    //创建备用对象
    BackupTestMessage * backUp = [BackupTestMessage new];
    SEL sel = anInvocation.selector;
    //判断备用对象是否可以响应传递进来等待响应的SEL
    if ([backUp respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:backUp];
    }else{
//    如果备用对象不能响应 则抛出异常
        [self doesNotRecognizeSelector:sel];
    }
}

runtime可以对崩溃做出补救,使用上面三种方法都可以实现,但是我们选着那种合适呢?

实际用途

    //根据类名反射出我们想要的类
    Class class = NSClassFromString(@"OneViewController");
    //根据方法反色出我们想要的方法
    SEL selector = NSSelectorFromString(@"viewDidLoad");

    //新增方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
    //替换方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);

   //  在runtime中动态注册类
    Class superCls = NSClassFromString(superClassName);
    cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
    objc_registerClassPair(cls);

JSPath在实现中是通过创建一个class同名对象,当向OC发送alloc消息之后,会将OC环境中创建的对象地址保存到这个这个js同名对象中,js本身并不完成任何对象的初始化。

那对象是怎样调用方法的呢?

在执行JS脚本之前,先把所有方法的名字替换成_c(‘method’)的方式,使用的是OC消息转发机制,给JS对象基类添加__c方法,这样所有对象都可以调用到__c,在根据对象类型进行不同的操作。

JS怎样替换OC方法?

消息转发实现多继承

@implementation Programmer

// 通过消息转发实现多继承
- (id)forwardingTargetForSelector:(SEL)aSelector {
    Singer *singer = [[Singer alloc] init];
    Artist *artist = [[Artist alloc] init];
    
    if ([singer respondsToSelector:aSelector]) {
        return singer;
    }
    else if ([artist respondsToSelector:aSelector]) {
        return artist;
    }
    return nil;
}
@end

@interface Artist : NSObject
// 画家可以绘画
- (void)draw;
@end

@implementation Artist
- (void)draw {
    NSLog(@"I can draw");
}
@end

@interface Singer : NSObject
// 歌手会唱歌
- (void)sing;
@end

@implementation Singer
- (void)sing {
    NSLog(@"Lalalalalala");
}
@end

//调用
Programmer *programmer = [[Programmer alloc] init];
// 或者通过类型强转来实现,无警告
[(Artist *)programmer draw];
[(Singer *)programmer sing];
上一篇 下一篇

猜你喜欢

热点阅读