iOS底层原理之消息转发机制探究

581 阅读4分钟

我们在iOS底层原理之方法慢速查找探究中提到了如果找不到消息就会提供一次补救机会,即这就涉及到了消息转发机制

动态决议resolveMethod

我们点击resolveMethod_locked进入

实例方法动态决议 resolveInstanceMethod

我们还是以上一篇中LGPerson为例子,代码如下

//.h声明方法
@interface LGPerson : NSObject
- (void)sayHello;
@end
//.m不实现sayHello
#import <objc/message.h>

@implementation LGPerson

-(void)sayMaster {
    NSLog(@"动态决议来了");
}
+(BOOL)resolveInstanceMethod:(SEL)aSelector {
    NSLog(@"进来了");
    if (@selector(sayHello) == aSelector) {
        
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, aSelector, imp, type);
    }
    return [super resolveInstanceMethod:aSelector];
}
@end

//main函数调用sayHello
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGPerson *person = [LGPerson alloc];
        [person sayHello];

    }
    return 0;
}

代码执行效果 这样通过实例方法补救,避免了崩溃

类方法动态决议 resolveClassMethod

同样道理,我们在LGPerson里面声明类方法sayNB,但不实现,我们在LGPerson中实现resolveClassMethod方法

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayNB;
@end
+(void)say666 {
    NSLog(@"say666来了");
}
+ (BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"类方法来了");
    if (@selector(sayNB) == sel) {

        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(say666));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(say666));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return [super resolveClassMethod:sel];
}

执行结果 注意

  • 类方法存储在元类中,所以类方法对于元类对象说属于元类的实例方法,因此resolveClassMethod可以写在LGPerson的元类中,由于元类无法写入,我们可以写在NSObject的分类中,代码如下
//直接实例方法即可
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"消息动态决议来了");
    if (@selector(sayHello) == sel) {
        
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }else if (@selector(sayNB) == sel) {

        IMP imp           = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(say666));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(say666));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return NO;
}

执行结果

动态决议resolveMethod总结

  • 实例方法的resolveInstanceMethod可以在当前类里实现
  • 类方法的resolveClassMethod中一定要添加的是类,且方法也需要是类方法
  • 类方法是其元类对象的实例方法,因此在根元类里面可以直接处理resolveInstanceMethod即可,但在根元类处理坏处就是需要判断方法名称,不便于后期维护

快速消息转发forwardingTargetForSelector

在源码中我们搜索forwardingTargetForSelector,搜索结果如下从搜索结果中我们可以看到forwardingTargetForSelector只有实例方法的快速转发,没有类方法快速转发(NSObject的.h文件没有类方法),然而真的不能类方法快速转发吗?我们先看实例方法的快速转发。

实例方法的快速转发

LGPerson的实例方法sayHello让LGTeacher来实现

@interface LGTeacher : NSObject
- (void)sayHello;
@end

@implementation LGTeacher
-(void)sayHello {
    NSLog(@"代替LGPerson执行了 %s",__func__);
}
@end

在LGPerson的.m文件中

-(id)forwardingTargetForSelector:(SEL)aSelector {
    if (@selector(sayHello) == aSelector) {
        
        return [LGTeacher alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

重写forwardingTargetForSelector方法,拦截到sayHello时让LGTeacher来实现,执行结果如下

类方法的快速转发

我们将实例方法的快速转发代码修改如下

+(id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"他来了他来了");
    if (@selector(sayNB) == aSelector) {
        
        return [LGTeacher class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

查看执行结果什么情况,它竟然实现了类方法的快速转发

  • 为什么会执行类方法的快速转发目前猜测:虽然没有声明类方法的forwardingTargetForSelector,但在NSObject的.m文件中有该方法的实现,由于类对象存储在元类当中,最终会从NSObject中找到forwardingTargetForSelector的类方法,且由于LGPerson重写了forwardingTargetForSelector的类方法,因此得以实现。
  • 证明猜测: 我们在LGPerson的.m文件中添加类方法say888,让LGTeacher继承LGPerson,代码如下所示-LGPerson LGTeacher
  • LGTeacher不重写say888的执行结果
  • LGTeacher重写say888的执行结果
  • 总结:当父类.h文件没有声明类方法,.m文件实现了方法,继承与父类的子类,依然可以调用父类.m中的方法;子类重写了父类的方法,则执行子类重写的方法。

消息慢速转发 methodSignatureForSelector

代码实现

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(sayHello)) {
        return [[LGTeacher alloc] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation invoke];
}

执行结果 注意

  • methodSignatureForSelector中不可返回nil,否则崩溃,我们这儿使用了 LGTeacher来实现sayHello,也可以自定义方法,如直接返回:[NSMethodSignature signatureWithObjCTypes:"v@:"];
  • forwardInvocation配合着methodSignatureForSelector使用

消息转发机制总结