Runtime的实践——方法交换
2016-05-12 13:05
471 查看
读过《Runtime的初步认识——结构体与类》的小伙伴们应该对objc_class结构体的构造有所了解了
在这里,我们可以找到
实例变量
方法列表
缓存方法列表
在这里插一嘴。我们之前在《Runtime的初步认识——消息机制》中介绍过,在Objective-C里面调用一个方法的时候,runtime层会将这个调用翻译成
而
这TMD什么鬼??? 保证看完你是这个反应。。。Apple为了高度优化这些方法的性能,这些方法都是汇编写成的。不过虽然我们看不懂汇编。但是通过注释我们也能了解消息分发的大概逻辑了。(这个地方我们可以先简单了解到这,感兴趣的可以继续研究,一起分享交流)接下来我们切回正题。
当我们向对象发送消息的时候,OC会到缓存方法列表中开始找方法的指针,如果缓存列表中找不到,就会到方法列表中找,如果本类的方法列表中找不到,就会到父类里面找,直到找到方法的指针或者最终的父类
当找到方法指针的时候,OC会将会在内存中找到方法指针所指向的那个代码块,并运行它。
我们知道,程序之所以能运行,是因为方法和变量都是存在程序的内存中。所以如果我们改变了方法指针指针所指向的内存地址的内容或者直接改变了方法指针指向的地址,我们就可以改变了方法的实现。
这个函数是
这个方法的注释官方已给出, 我们只要关注他的参数以及返回值(由于返回值为void, 所以在此没有多余的解释, 主要解释两个参数)。
现在这两个参数是我们平时看的见的参数。综上所述,我们只要将两组要交换的方法的
由此最终的代码会变成:
如果你不知道方法交换的最终效果,现在我们用一个很简单的例子来说明这个问题。
比如我们现在有两个类的文件,每个类都有自己的方法和实现。
正常情况下
如果我们调用
同理如果调用
但是如果我们在某一个时刻执行了
在此之后(直到程序结束前),我们运行
还有个问题就是我们到底应该在什么地方调用切换方法的代码呢?
我们还要了解每个类都有个
相关推荐:
《Runtime的实践——给一个类添加属性(关联对象)》
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
在这里,我们可以找到
实例变量
struct objc_ivar_list *ivars,
方法列表
struct objc_method_list **methodLists,
缓存方法列表
struct objc_cache *cache。
在这里插一嘴。我们之前在《Runtime的初步认识——消息机制》中介绍过,在Objective-C里面调用一个方法的时候,runtime层会将这个调用翻译成
objc_msgSend(id self, SEL op, ...)
而
objc_msgSend具体有事如何分发的呢?我们来看下runtime层
objc_msgSend的源码。(runtime的源代码可以在 http://opensource.apple.com//tarballs/objc4/ 下载)
/******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); * ********************************************************************/ ENTRY _objc_msgSend MESSENGER_START CALL_MCOUNTER // load receiver and selector movl selector(%esp), %ecx movl self(%esp), %eax // check whether selector is ignored cmpl $ kIgnore, %ecx je LMsgSendDone // return self from %eax // check whether receiver is nil testl %eax, %eax je LMsgSendNilSelf // receiver (in %eax) is non-nil: search the cache LMsgSendReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup WORD_RETURN, MSG_SEND, LMsgSendCacheMiss xor %edx, %edx // set nonstret for msgForward_internal MESSENGER_END_FAST jmp *%eax // cache miss: go search the method lists LMsgSendCacheMiss: MethodTableLookup WORD_RETURN, MSG_SEND xor %edx, %edx // set nonstret for msgForward_internal jmp *%eax // goto *imp // message sent to nil: redirect to nil receiver, if any LMsgSendNilSelf: // %eax is already zero movl $0,%edx xorps %xmm0, %xmm0 LMsgSendDone: MESSENGER_END_NIL ret // guaranteed non-nil entry point (disabled for now) // .globl _objc_msgSendNonNil // _objc_msgSendNonNil: // movl self(%esp), %eax // jmp LMsgSendReceiverOk LMsgSendExit: END_ENTRY _objc_msgSend
这TMD什么鬼??? 保证看完你是这个反应。。。Apple为了高度优化这些方法的性能,这些方法都是汇编写成的。不过虽然我们看不懂汇编。但是通过注释我们也能了解消息分发的大概逻辑了。(这个地方我们可以先简单了解到这,感兴趣的可以继续研究,一起分享交流)接下来我们切回正题。
当我们向对象发送消息的时候,OC会到缓存方法列表中开始找方法的指针,如果缓存列表中找不到,就会到方法列表中找,如果本类的方法列表中找不到,就会到父类里面找,直到找到方法的指针或者最终的父类
NSObject也找不到方法的指针为止。当找不到方法指针的时候,编译器会发出
[XXXX 某方法]unrecognized selector sent to instance 0x100400d90的警告。
当找到方法指针的时候,OC会将会在内存中找到方法指针所指向的那个代码块,并运行它。
我们知道,程序之所以能运行,是因为方法和变量都是存在程序的内存中。所以如果我们改变了方法指针指针所指向的内存地址的内容或者直接改变了方法指针指向的地址,我们就可以改变了方法的实现。
Runtime中的方法交换
Runtime给了我们一个函数来实现方法交换,你只需要导入objc/Runtime.h文件即可使用这个函数。
这个函数是
/** * Exchanges the implementations of two methods. * 交换两个方法的实现 * * @param m1 Method to exchange with second method. * @param m2 Method to exchange with first method. * * @note This is an atomic version of the following: * 这个函数的实现如下: * \code * IMP imp1 = method_getImplementation(m1); * IMP imp2 = method_getImplementation(m2); * method_setImplementation(m1, imp2); * method_setImplementation(m2, imp1); * \endcode */ void method_exchangeImplementations(Method m1, Method m2);
这个方法的注释官方已给出, 我们只要关注他的参数以及返回值(由于返回值为void, 所以在此没有多余的解释, 主要解释两个参数)。
Method是Objective-C语言中的一个结构体, 在
runtime.h头文件中有定义. 在这个函数中,
Method顾名思义就是要交换的方法. 我们可以通过下面这个函数来获取一个类的
Method。
Method class_getInstanceMethod(Class cls, SEL name);
现在这两个参数是我们平时看的见的参数。综上所述,我们只要将两组要交换的方法的
SEL和该方法所在的
Class传入进去即可实现方法交换。
由此最终的代码会变成:
Method m1 = class_getInstanceMethod([M1 class], @selector(method1name)); Method m2 = class_getInstanceMethod([M2 class], @selector(method2name)); method_exchangeImplementations(m1, m2);
如果你不知道方法交换的最终效果,现在我们用一个很简单的例子来说明这个问题。
比如我们现在有两个类的文件,每个类都有自己的方法和实现。
@interface classOne : NSObject @end @implementation classOne() - (void)methodOne { NSLog(@"methodOne"); } @end
@interface classTwo : NSObject @end @implementation classTwo() - (void)methodTwo { NSLog(@"methodTwo"); } @end
正常情况下
如果我们调用
[[classOne new] methodOne]则会输出
methodOne。
同理如果调用
[[classTwo new] methodTwo]则会输出
methodTwo。
但是如果我们在某一个时刻执行了
一次下面的代码
Method method1 = class_getInstanceMethod([classTwo class], @selector(methodTwo)); Method method2 = class_getInstanceMethod([classOne class], @selector(methodOne)); method_exchangeImplementations(method1, method2);
在此之后(直到程序结束前),我们运行
[[classOne new] methodOne]的时候,打印的是
methodTwo。
这个就是runtime的黑科技,慎用~
这个就是runtime的黑科技,慎用~
这个就是runtime的黑科技,慎用~
通过以上内容, 你应该可以深刻体会到OC为什么是一个有运行时特色的语言了还有个问题就是我们到底应该在什么地方调用切换方法的代码呢?
我们还要了解每个类都有个
load方法,这个方法是类加载到内存是调用的,所以我们可以在任意一个类的
load方法里写这个函数。
相关推荐:
《Runtime的实践——给一个类添加属性(关联对象)》
相关文章推荐
- 素数距离问题
- 快速打开Android虚拟机的方法
- 用Python和OpenCV提取颜色直方图特征
- 韩信点兵
- Java SpringMVC实现国际化整合案例分析(i18n)
- 操作系统 实验三 进程调度模拟程序
- Python的词法分析与语法分析
- 网易实习生笔试题 - 卡密模糊匹配
- codeforces557D 二分图+奇环判断
- 深入浅出: Java回调机制(异步)
- python的string模块中的Template
- PageRank算法在spark上的简单实现
- AP第四贴 实拍曾经的15300ST,爱彼,爱表族论坛 - 爱表族
- VS2010启动多个实例调试
- C语言版本--工资管理系统
- memset calloc malloc
- Spring Security 学习笔记-授权控制过滤器
- Codeforces Round #352 (Div. 2) C - Recycling Bottles
- 判断一个字符串是否全是数字
- json需要的jar包