聊聊Method Swizzling

Method Swizzling算是Objective-C的runtime中常用的一个黑科技方法。

Method Swizzling是用来改变/替换一个已经存在的 实例/类 方法的实现。

###概括
在Objective-C的类中,存在一个方法列表(dispatch table)。一个类/实例在调用一个方法的时候,会以@selector(methodName)为索引在方法列表中查询对应的实现

img

(出处见水印)

IMP对应一个方法的实现,类似于函数指针。

Runtime提供了几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1、获取类cls的实例方法@selector(name)的实现IMP
IMP class_getMethodImplementation(Class cls, SEL name);
//2、把类cls中的@selector(name)方法的实现替换为imp, types为表述此方法的参数类型的字符串.可以通过
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) ;
//3、把Method1与Method2替换
void method_exchangeImplementations(Method m1, Method m2) ;
//4、获取一个方法的参数描述字符串
const char *method_getTypeEncoding(Method m)
//5、向一个类中添加一个新的方法
//添加成功,返回YES,否则NO
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//还有很多....

具体可以参考苹果文档
比如用了第三个方法 method_exchangeImplementations那么交换之后的形式就为:

img
(出处见水印)

###例子
举个例子,当我们想要检测一个页面打开了多少遍,我们可以在 viewDidAppear 当中添加统计代码,但是要想检测很多界面的话,在每一个VC当中都添加这么一个统计代码实在麻烦。当然如果用继承来实现也是可以的,但是必须继承 UIViewController, UITableViewController, UINavigationController等好几个,也是麻烦的很。
这个时候就可以使用OC的黑魔法了——-Method Swizzling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
/*
** + (void)load 方法,是当此类被引用链接的时候就会调用的一个方法
*/
+ (void)load {
//采用一个Singleton来进行Method Swizzling,保证只运行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
//通过方法名来获取对应的Method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 如果是类方法,不是实例方法,用如下
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
//贴此class_addMethod的一个官方注释,不翻译了...
//@return YES if the method was added successfully, otherwise NO
//(for example, the class already contains a method implementation with that name).
//@note class_addMethod will add an override of a superclass's implementation,
//but will not replace an existing implementation in this class.
//To change an existing implementation, use method_setImplementation.
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end

现在任何一个ViewController或者其子类调用 viewWillAppear: 这个方法都会进入我们的xxx_viewWillAppear: 打印出log

注意点⚠️:我们的 xxx_viewWillAppear: 的实现看起来像是递归一样,但是因为我们做了Method Swizzling,所以我们的实现的内部调用 xxx_viewWillAppear: 其实是调用原来的 viewWillAppear: ,所以不会发生递归。

###+ (void)load VS + (void)initialize

Method Swizzling 永远应该在 + (void)load当中实现!
这两个方法都是每一个类在Objective-C的runtime的时候会被自动调用的方法
第一个load会在每一个类文件在第一次被链接的时候就会调用,也就是说只要它存在就会被调用一次。
第二个initialize只有在程序第一次调用了这个类的某一个方法或者这个类被实例化的时候(即在程序第一次向这个类发消息)才会被调用,如果用不到这个类,这个方法就不会被调用。
因为Method Swizzling是面向全局的。所以为了保证被执行,应该放在load里面。

###Selectors, Methods, & Implementations

在Objective-C当中,selectors,methods, 还有 implementations,是runtime中常常出现的几个概念。
先贴一段苹果官方的说明

  • Selector (typedef struct objc_selector *SEL):

    Selectors are used to represent the name of a method at runtime.

    A method selector is a C string that has been registered (or “mapped”) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded .

  • Method (typedef struct objc_method *Method):

    An opaque type that represents a method in a class definition.

  • Implementation (typedef id (*IMP)(id, SEL, …)):

    This data type is a pointer to the start of the function that implements the method.

    This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.

其实很好理解,就是每一个类(Class)都有一个方法列表,以便在runtime的时候发送消息。表中的每一行是一个方法(Method),其中以一个特定的名字selector(SEL)为索引,指向这个方法的具体实现的函数指针(IMP)
Method Swizzling危险么?

乍一看很危险,但是尖锐的刀子更安全,所以还是看如何使用。



找到一个不错的问答:stackOverFlow上面关于Method Swizzling的讨论

###小结
这一篇浅谈了一下Method Swizzling的基本概念与基本使用,只是简单介绍了Method Swizzling的概念,使用与优缺点,具体的应用随后总结完后再写出来

###引用
http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c


http://nshipster.com/method-swizzling/


http://blog.csdn.net/yiyaaixuexi/article/details/9374411


https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/