runtime之消息转发

前言

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

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

1 objc_property_t *class_copyProperty(Class cls,unsigned int *outcout)        //获取所有的属性列表
2 Method *class_copyMethodList(Class cls,unsigned int *outCount)          //获取所有方法的数组
3 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中如果还没有找到的话会按照优先级从高到低调用下面三个函数:

1   + 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中实现消息转发:

 1 +(BOOL)resolveInstanceMethod:(SEL)sel
 2 {
 3     NSString *selString = NSStringFromSelector(sel);
 4     if ([selString isEqualToString:@"goForWork"]) {
 5         /**
 6          *   为类中没有实现的方法添加一个函数实现
 7          *
 8          *  @param self      类名
 9          *  @param goForWork 没有实现的方法
10          *  @IMP   workFunc  添加的函数实现
11          *  @      "[email protected]:"     TypeEncoding函数类型的类型编码
12          *  @return
13          */
14         class_addMethod(self, @selector(goForWork), (IMP)workFunc, "[email protected]:");
15     }
16     return [super resolveInstanceMethod:sel];
17 }
18
19 void workFunc(id self,SEL sel)
20 {
21     NSLog(@"Person go for work");
22 }

在外界调用Person实例的goForWalk 方法,可以看见打印台打印:

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

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

转换消息对象方式二:

methodSignatureForSelector:和forwardInvocation:结合实现消息转发。
 1 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
 2 {
 3     NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
 4     if (!methodSignature) {
 5         methodSignature = [Dog instanceMethodSignatureForSelector:aSelector];
 6     }
 7     return methodSignature;
 8 }
 9
10 - (void)forwardInvocation:(NSInvocation *)anInvocation
11 {
12     if ([Dog instancesRespondToSelector:anInvocation.selector]) {
13         //消息调用
14         [anInvocation invokeWithTarget:self.myDog];
15
16     }
17 }

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

runtime之方法交换实现:

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

假设有两个方法A和B,正常情况下我发送A消息调用的是A的实现,发送B消息的时候调用的是B的实现。所谓的方法交换实现,就是我给对象发送了A消息调用的却是B的实现,给对象发送B消息调用的却是A的实现。难道这就是传说中的移花接木??

runtime下提供了一系列的函数来助我们修炼移魂大法:

Method class_getInstanceMethod(Class cls, SEL name)   //获取某一个实例方法
IMP class_getMethodImplementation(Class cls, SEL name)   //获取某一个方法的实现
const char *method_getTypeEncoding(Method m)     //获取一个方法类型的类型编码
IMP class_replaceMethod(Class cls, SEL name, IMP imp,
                                    const char *types)           //用一个方法的实现来代替另一个方法的实现
void method_exchangeImplementations(Method m1, Method m2)    //交换两个方法的实现

思路大概就跟我们动态添加方法实现的思路一样,当然你也可以霸王硬上弓,一上来二话不说直接就两个方法交换实现,但是如果此时有两个方法其中有一个没有实现呢???会发现什么??

Person.h中定义以下两个方法并在 Person.m文件中做以下实现

-(void)drink
{
    NSLog(@"我在喝水");
}
-(void)eat
{
    NSLog(@"我在吃东西");
}

同时在初始化函数中我们对其这两个方式进行“移花接木”:

 1 + (Person *)personWithName:(NSString *)name age:(NSNumber *)age gender:(NSString *)gender clan:(NSString *)clan
 2 {
 3     Person *p = [Person new];
 4     unsigned int outCount;
 5     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
 6     object_setIvar(p, IvarArray[0], clan);
 7     object_setIvar(p, IvarArray[1], name);
 8     object_setIvar(p, IvarArray[2], gender);
 9     object_setIvar(p, IvarArray[3], age);
10     //以上为runtime下的成员变量操作,具体可看 上一篇
11     static dispatch_once_t onceToken;
12     dispatch_once(&onceToken, ^{
13         Class selfClass = [self class];
14         SEL aSel = @selector(drink);
15         Method aMethod = class_getInstanceMethod(selfClass, aSel);
16         SEL bSel = @selector(eat);
17         Method bMethod = class_getInstanceMethod(selfClass, bSel);
18         //依次先获取两个方法的SEL指针和runtime的Method
19         BOOL value = class_addMethod(selfClass, aSel, method_getImplementation(bMethod), method_getTypeEncoding(bMethod));
20         //将B的实现添加到A身上
21         if (value) {
22             class_replaceMethod(selfClass, bSel, method_getImplementation(aMethod), method_getTypeEncoding(aMethod));
23         }else{
24             method_exchangeImplementations(bMethod, aMethod);
25         }
26     });
27     return p;
28 }

在外界给person对象发送drink消息的时候打印台:

同样的当发送eat消息时候打印出来的是"我在吃东西"。一般情况下两个方法交换实现在实际需求中还是比较少见的,特别是我们自定义的方法中的时候容易出现不明甚至问题复杂的情况。想想要是有哪个心计boy在离职的话在程序埋了这样一个不会爆炸的炸弹的话。。。。。好腹黑

不过要是这种移花接木应用的好的话倒是在某些特定的场合能够省了不少事,比如你想更新版本发现素材比原来的素材名字上多了同样的前缀而已,这时候可以在为imageNamed添加一个方法实现为所有素材的名字加上一个同样的前缀。这样当程序调用imageWithNamed的时候就会调用你自定义的函数实现,轻松的更新素材的名字,而不用到程序中一个个查找手动添加前缀。又或者你刚接收公司项目需要整天查看项目的框架,一般情况下你或许会在所有的viewDidLoad添加上几句log来查看,这时候就能排上用场了。

初尝runtime,若是有什么表述不当的地方还请指出。后续将继续更新runtime的学习。

戳我下载代码!

时间: 2024-11-09 05:09:35

runtime之消息转发的相关文章

objc_msgSend消息传递学习笔记 – 消息转发

该文是 objc_msgSend消息传递学习笔记 – 对象方法消息传递流程 的基础上继续探究源码,请先阅读上文. 消息转发机制(message forwarding) Objective-C 在调用对象方法的时候,是通过消息传递机制来查询且执行方法.如果想令该类能够理解并执行方法,必须以程序代码实现出对应方法.但是,在编译期间向类发送了无法解读的消息并不会报错,因为在 runtime 时期可以继续向类添加方法,所以编译器在编译时还无法确认类中是否已经实现了消息方法. 当对象接受到无法解读的消息后

iOS消息转发

消息转发是一种功能强大的技术,可以大大增加Objective-C的表现力.什么是消息转发?简而言之,它允许未知的消息被困住并作出反应.换句话说,无论何时发送未知消息,它??都会以一个很好的包发送到您的代码中,此时您可以随心所欲地执行任何操作. 为什么它被称为 "转发"? 当某个对象没有任何响应某个 消息 的操作就 "转发" 该 消息.原因是这种技术主要是为了让对象让其他对象为他们处理 消息,从而 "转发". 1. 类,对象,方法 在我们开始使用消

runtime总结二之消息机制(包括消息转发,消息交换的黑魔法)

runtime的消息机制 前面提到过编译器最终会把我们的消息发送转化为函数调用 消息发送 [object sendMassage] 首先编译器会在运行时将上面的例子转化为objc_msgSend(obj,@selector(sendMassage))这个函数,转换的时候除了方法本身的参数之外,还有两个隐藏的参数一个是id类型的,代表对象的类型,还是一个是SEL类型的,是函数对应的方法的编号,接下来就会按照下面的流程来调用这个方法 通过obj的isa指针找到其所对应的类. 通过SEL先去类的cac

runtime消息转发机制

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制.而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库.它是 Objective-C 面向对象和动态机制的基石. Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象.进行消息传递和转发.理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的

runtime 消息转发

消息转发机制 http://www.jianshu.com/p/801f4e86259f 脑图:http://naotu.baidu.com/viewshare.html?shareId=axevyea7pc4o Objective-C特性:Runtime http://www.cocoachina.com/ios/20150715/12540.html

RunTime(消息机制) + RunTime(消息转发)

一.消息机制 1.在viewDidLoad中直接用 performSelector:@selector(doSomething) 来调用doSomething方法时,会发现找不到这个方法而奔溃.此时,我们可以在resolveInsantanceMethod:(SEL)see 方法中获取这个所有在运行时阶段的方法,在这个方法中只需要判断一下,将这个方法获取,并且运用Runtime 的 class_addMethod 的方法来将方法和响应函数绑定,进而达到为某一个类添加方法的目的. - (void)

iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识,从原理到实践.由于包括内容过多分为下面五篇文章详细解说.可自行选择须要了解的方向: 从runtime開始: 理解面向对象的类到面向过程的结构体 从runtime開始: 深入理解OC消息转发机制 从runtime開始: 理解OC的属性property 从runtime開始: 实践Category加入属

IOS 消息转发

最近在看消息转发的资料,发现大部分都是理论知识,很少有完整的代码.现在以代码的形式形象的解释一下: 用Xcode创建一个工程 1.正常方法调用 创建一个类Person 代码如下 Person.h代码如下: #import <Foundation/Foundation.h> @interface Person : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithUserName:(NSString *)u

iOS的消息机制和消息转发

1.消息机制 RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何二义性.OC的函数调用成为消息发送.属于动态调用过程.在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错.而C语言在编译阶段就会报错).只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用. [ob