作用:改变已经存在selector的实现,之所以可以这样是因为方法调用可以再运行时改变:通过改变类的分发表(dispatch table, 该表包含selector的名称及对应实现函数的地址)里selector和实现之间的对应关系。
举个例子:比如想记录一个iOS应用里每个view controller显示的次数:可以在每个view controller添加记录的代码,但这会导致大量的重复代码;通过继承也是一个方法,但需要同时创建UIViewController、UITableViewController、UINavigationController及其它中view controller的子类,同样也会产生许多重复的代码出现。
幸运地是,在UIViewController的Category中使用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
|
//
// UIViewController+Swizzling.m
// SwizzlingDemo
//
// Created by 张庆杰 on 15/9/25.
// Copyright © 2015年 QJ. All rights reserved.
//
#import "UIViewController+Swizzling.h"
#import <objc/runtime.h>
@implementation
UIViewController
(Swizzling)
+ (
void
)load {
static
dispatch_once_t
onceToken;
dispatch_once
(&onceToken, ^{
Class
class
= [
self
class
];
SEL
originalSelector =
@selector
(viewWillAppear:);
SEL
swizzlingSelector =
@selector
(swizzling_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(
class
, originalSelector);
Method swizzlingMethod = class_getInstanceMethod(
class
, swizzlingSelector);
// 如果 swizzling 的是类方法,采用如下的方式:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledMethod);
// 交换实现
method_exchangeImplementations(originalMethod, swizzlingMethod);
});
}
#pragma mark - Method
- (
void
)swizzling_viewWillAppear:(
BOOL
)animated {
[
self
swizzling_viewWillAppear:animated];
NSLog
(
@"黑魔法~~~~~~~"
);
}
@end
|
Now, 当一个UIViewController或者其子类的实例调用viewWillAppera:方法时,就会打印一条记录。假如要在view controller的生命周期,view的绘制或者Foundation的网络协议栈注入一些自定的行为,method swizzling可以使考虑的一个方向。
使用method swizzling需要注意的点:
+load vs. +initialize
Swizzling应该只在load方法中使用
oc会在运行时自动调用每个类的两个方法,+load会在类初始化加载的时候调用;+initialize方法会在程序调用类的第一个实例或者类方法的时候调用。这两个方法都是可选的,只会在实现的时候才会去调用。由于method swizzling会影响到全局的状态,因此最小化竞争条件的出现变得很重要,+load方法能够确保在类的初始化时候调用,这能够保证改变应用行为的一致性,而+initialize在执行时并不提供这种保证,实际上,如果没有直接给这个类发送消息,该方法可能都不会调用到。
dispatch_once
Swizzling应该只在dispatch_once中完成
如上,由于swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是其中的一种预防措施,因为它能保证不管有多少个线程,代码只会执行一次。GCD的dispatch_once能够满足这种需求,因此在method swizzling应该将其作为最佳的实践方式。
调用_cmd
看起来下面的代码会导致无限循环:
1
2
3
4
5
6
7
8
9
|
#pragma mark - Method
- (
void
)swizzling_viewWillAppear:(
BOOL
)animated {
[
self
swizzling_viewWillAppear:animated];
NSLog
(
@"黑魔法~~~~~~~"
);
}
|
实际并不会。在swizzling的过程中,swizzling_viewWillAppear:已经被重新指向UIViewController的原始实现-viewWillAppear:,但是如果我们在这个方法中调用viewWillAppear:则会导致无限循环