您的位置:首页 > 其它

Runtime的实践——方法交换

2016-05-12 13:05 471 查看
读过《Runtime的初步认识——结构体与类》的小伙伴们应该对objc_class结构体的构造有所了解了

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的实践——给一个类添加属性(关联对象)》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: