runtime之消息转发

前言

在上一篇文章中我们初尝了runtime的黑魔法,可以在程序编译阶段就获取到成员变量的名字,特性以及动态的给对象增加属性等等,在接下来中我们进一步了解OC的消息发送机制。如果之前没接触过runtime的同学建议先看看:上一篇《runtime之玩转成员变量》

OC的消息发送机制是早有耳闻,鉴于自己一直觉得是很底层的东西需要花大量的时候去学习研究它所以一直都是蠢蠢欲动。同样不做过多铺垫,直接切入吧。当我们使用OC对象调用一个方法的时候,比如这样:[lisi  sayHello];  程序运行的时候会转化为runtime的代码objc_msgnSend(lisi,@selector(sayHello)),通过消息发送函数的字面意义我们可以知道是给lisi这个对象发送了sayHello这个消息。方法的调用其实就是给类发送一个消息,调用类方法也一样,类实际上也是一个对象,是元类的实例。runtime中类似这种消息发送的函数还有很多包括:

 objc_property_t *class_copyProperty(Class cls,unsigned int *outcout)        //获取所有的属性列表
Method *class_copyMethodList(Class cls,unsigned int *outCount) //获取所有方法的数组
Bool class_addMethod (Class cls,SEL name,IMP imp,const char *type) //添加方法

消息转发流程:

当我们创建一个实例变量并调用实例方法时候,即[receiver  message],转换为运行时代码id objc_msgSend(id self,SEL op....),首先根据实例的isa指针到指定的类中的方法列表中进行查找相应的op,如果找到相应的op则调用,如果找不到的话则到相应的父类中查找,这样一直循环上去,一直到根类NSObject中如果还没有找到的话会按照优先级从高到低调用下面三个函数:

   + resolveInstanceMethod:(SEL)sel //     对应实例方法没有获取到
+ resolveClassMethod:(SEL)sel // 对应类方法没有获取到
2 - (id)forwardingTargetForSelector:(SEL)aSelector
3 - (void)forwardInvocation:(NSInvocation *)anInvocation

即某一个实例方法的本类及其父类都没有实现的时候会首先调用+ resolveInstanceMethod:(SEL)sel,如果该方法没有实现则调用- (id)forwardingTargetForSelector:(SEL)aSelector,如果第二个方法还没有实现的时候就调用第三个- (void)forwardInvocation:(NSInvocation *)anInvocation。若是这三个方法都没有实现的话则程序抛出异常。

注意:第三个方法(void)forwardInvocation:(NSInvocation *)anInvocation需要跟methodSignatureForSelector结合使用才能实现消息转发,methodSignatureForSelector的作用是为一个类已经实现的方法创建一个有效的签名。

消息转发的原理:

每个类都有一个包含SEL和对应的IMP的Method列表,也就是说一个Method包含着一个SEL和一个对应的IMP,而消息转发就是将原本的SEL和IMP的这种对应关系给分开,跟其他的Method重新组合。

下面通过一个Person类体验实现runtime的消息转发:

1,动态添加函数实现消息转发:

Person.h添加下面在这个方法并且在Person.m文件中不实现它:

- (void)goForWork;     

在Person.m中实现消息转发:

 +(BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *selString = NSStringFromSelector(sel);
if ([selString isEqualToString:@"goForWork"]) {
/**
* 为类中没有实现的方法添加一个函数实现
*
* @param self 类名
* @param goForWork 没有实现的方法
* @IMP workFunc 添加的函数实现
* @ "v@:" TypeEncoding函数类型的类型编码
* @return
*/
class_addMethod(self, @selector(goForWork), (IMP)workFunc, "v@:");
}
return [super resolveInstanceMethod:sel];
} void workFunc(id self,SEL sel)
{
NSLog(@"Person go for work");
}

在外界调用Person实例的goForWalk 方法,可以看见打印台打印:runtime之消息转发

2,切换消息接受者实现消息转发:

将消息给其他对象也是消息转发的一种形式,一般是将消息转发给该对象中其他对象,这样子看起来也就感觉是该对象执行了该方法。我们在Person类中定义一个Dog类型的变量myDog。同样的,在Person定义一个walk方法并且不实现,Dog类同样定义这样的一个方法并在implement中实现。

Person.m中重写forwardingTargetForSelector:转换消息接收者:

-(id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"walk"]) {
self.myDog = [Dog new];
return self.myDog;
}
return [super forwardingTargetForSelector:aSelector];
}

外界调用后可以在打印台中:

runtime之消息转发

转换消息对象方式二:

methodSignatureForSelector:和forwardInvocation:结合实现消息转发。
 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [Dog instanceMethodSignatureForSelector:aSelector];
}
return methodSignature;
} - (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([Dog instancesRespondToSelector:anInvocation.selector]) {
//消息调用
[anInvocation invokeWithTarget:self.myDog]; }
}

通过这种方式同样能够在打印台打印出相同结果。

runtime之方法交换实现:

当我学习到runtime这个移魂大法的时候不禁惊叹runtime大法好,简直是黑魔法,同时心里产生一点邪恶的心里

上一篇:leetcode415---字符串大数相加


下一篇:调整UILabel行高间距并返回自定义宽高度