OC中的 Method Swizzling
iOS中的黑魔法 Method Swizzling经常会用到,在OC中的使用方式是重写类的+ (void)load
方法,运行时交换方法
+ (void)_load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SwizzleMethod([self class],
@selector(originalMethod),
@selector(trans_originalMethod));
});
}
void SwizzleMethod(Class c, SEL origSEL, SEL newSEL) {
Method origMethod = class_getInstanceMethod(c, origSEL);
Method newMethod = nil;
if (!origMethod) {
origMethod = class_getClassMethod(c, origSEL);
if (!origMethod) {
return;
}
newMethod = class_getClassMethod(c, newSEL);
if (!newMethod) {
return;
}
}else{
newMethod = class_getInstanceMethod(c, newSEL);
if (!newMethod) {
return;
}
}
// 自身已经有了就添加不成功,直接交换即可
if(class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))){
class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}else{
method_exchangeImplementations(origMethod, newMethod);
}
}
其实在
load
方法中,可以不用使用GCD的dispatch_once
方法,因为load
本身就是只执行一次(方法交换需要且只能执行一次)
Swift中的 Method Swizzling
由于Swift中没有load
方法,所以只能写在initialize
中,这是调用类中第一个方法前的地方
extension UIScrollView {
public override static func initialize() {
struct Static {
static var token: dispatch_once_t = 0
}
// Make sure not subclass
if self !== UIScrollView.self {
return
}
dispatch_once(&Static.token) {
let originalSelector = NSSelectorFromString("handlePan:")
let swizzledSelector = NSSelectorFromString("pm_handlePan:")
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}
// MARK: - Method Swizzling
func pm_handlePan(pan: UIPanGestureRecognizer) {
pm_handlePan(pan)
if delegate != nil && delegate!.respondsToSelector(NSSelectorFromString("scrollViewDidPan:")) {
delegate?.performSelector(NSSelectorFromString("scrollViewDidPan:"), withObject: pan)
}
}
}
不过,Swift使用Method Swizzling需要满足两个条件
- 包含 swizzle 方法的类需要继承自 NSObject
- 需要 swizzle 的方法必须有动态属性(dynamic attribute)
通常添加下,我们都是交换系统类的方法,一般不需要关注着两点。但是如果我们自定义的类,也需要交换方法就要满足这两点了(这种状况是极少的,也是应该避免的)
更多详情请查看苹果文档
那么,自定义的类就得满足动态派发的特性
当@objc
属性将你的Swift API暴露给Objective-C runtime时,不能确保属性、方法、初始器的动态派发。Swift编译器可能为了优化你的代码,而绕过Objective-C runtime。当你指定一个成员变量为dynamic
时,访问该变量就总会动态派发了。因为在变量定义时指定dynamic
后,会使用Objective-C runtime来派发。
所以,需要对想要替换的方法声明为dynamic
。如下:
class MyOwnClass : NSObject {
dynamic func methodOriginal()->Int{
return 1
}
}
extension MyOwnClass {
//在 Objective-C 中,我们在 load() 方法进行 swizzling。但Swift不允许使用这个方法。
override class func initialize() {
struct Static {
static var token: dispatch_once_t = 0;
}
dispatch_once(&Static.token) {
let originalSelector = Selector("methodOriginal");
let swizzledSelector = Selector("methodSwizzled");
let originalMethod = class_getInstanceMethod(self, originalSelector);
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
func methodSwizzled()->Int{
// swizzling 后, 该方法就不会递归调用
return methodSwizzled()+1
}
}
var c = MyOwnClass()
print(methodOriginal()) //2
print(methodSwizzled()) //1
iOS中的黑魔法 Method Swizzling 不能随便用,只有在很了解并且比较特殊的情况才能使用,否则会有副作用的。