(详情: http://blog.jobbole.com/79580/)
Method Swizzling是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。
使用原因:
1.例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个viewcontroller的viewDidAppear中添加跟踪代码;但是这太过麻烦,需要在每个view controller中写重复的代码。创建一个子类可能是一种实现方式,但需要同时创建UIViewController, UITableViewController, UINavigationController及其它UIKit中view controller的子类,这同样会产生许多重复的代码。
2.iOS系统方法总是有可能因为越界而让程序崩溃(crash),我们这时可以考虑自己写方法替换系统的方法了。
刨根问底Objective-CRuntimehttp://www.cocoachina.com/ios/20141224/10740.html
MethodSwizzling
我们可以使用苹果的“黑魔法”Method Swizzling,Method Swizzling本质上就是对IMP和SEL进行交换。
MethodSwizzling原理
Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
而且Method Swizzling也是iOS中AOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。
首先,让我们通过两张图片来了解一下Method Swizzling的实现原理

(图一)

(图二)
上面图一中selector2原本对应着IMP2,但是为了更方便的实现特定业务需求,我们在图二中添加了selector3和IMP3,并且让selector2指向了IMP3,而selector3则指向了IMP2,这样就实现了“方法互换”。
在OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。
在每个类中都有一个Dispatch Table,这个Dispatch Table本质是将类中的SEL和IMP(可以理解为函数指针)进行对应。而我们的Method Swizzling就是对这个table进行了操作,让SEL对应另一个IMP。
代码:
@implementationNSArray(Extension)
+ (void)load {
static dispatch_once_tonceToken;
dispatch_once(&onceToken,^{
[self swizzleInstanceMethod:objc_getClass("__NSArrayI")originSelector:@selector(objectAtIndex:)otherSelector:@selector(safe_objectAtIndex:)];
});
}
/**
* 用来替换取出数组的元素的方法
*
* @param index 数组下标
*
* @return 返回取出的值,如果越界返回nil
*/
- (instancetype)safe_objectAtIndex:(NSUInteger)index {
if (self.count - 1< index) {
@try {
return [self safe_objectAtIndex:index];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n",class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {
}
} else {
return [self safe_objectAtIndex:index];
}
}
@end
看到上面的代码,肯定有人会问: 你太粗心了,你在safe_objectAtIndex方法中又调用了[selfsafe_objectAtIndex:index];
;,这难道不会产生递归调用吗?
答:然而....并不会。
还记得我们上面的图一和图二吗?Method Swizzling的实现原理可以理解为”方法互换“。假设我们将A和B两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。
例如我们上面的代码,系统调用NSArray的objectAtIndex方法时,实际上执行的是我们实现的safe_objectAtIndex方法。而我们在safe_objectAtIndex方法内部调用[selfsafe_objectAtIndex:index];时,执行的是NSArray的objectAtIndex方法。
MethodSwizzling类簇
之前我也说到,在我们项目开发过程中,经常因为NSArray数组越界或者NSDictionary的key或者value值为nil等问题导致的崩溃,对于这些问题苹果并不会报一个警告,而是直接崩溃,感觉苹果这样确实有点“太狠了”。
由此,我们可以根据上面所学,对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等类进行Method Swizzling,实现方式还是按照上面的例子来做。但是....你发现MethodSwizzling根本就不起作用,代码也没写错啊,到底是什么鬼?
这是因为Method Swizzling对NSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。
所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。
大家发现了吗,我的代码例子中,__NSArrayI才是NSArray真正的类,而NSMutableArray又不一样。我们可以通过runtime函数获取真正的类:
objc_getClass("__NSArrayI")
NSLog(@"%@", [@"ygf" class]);
NSLog(@"%@", [[NSMutableString stringWithFormat:@"ygf"] class]);
NSLog(@"%@", [[NSArray arrayWithObjects:@"ygf", nil] class]);
NSLog(@"%@", [[NSMutableArray arrayWithObjects:@"ygf", nil] class]);
NSLog(@"%@", [[NSDictionary dictionaryWithObjectsAndKeys:@"ygf", @"name", nil]class]);
NSLog(@"%@", [[NSMutableDictionary dictionaryWithObjectsAndKeys:@"ygf",@"name", nil] class]);
2016-04-06 18:07:49.380 YGFSwizzling[6600:226036]__NSCFConstantString
2016-04-06 18:07:49.380 YGFSwizzling[6600:226036]__NSCFString
2016-04-06 18:07:49.381 YGFSwizzling[6600:226036]__NSArrayI
2016-04-06 18:07:49.381 YGFSwizzling[6600:226036]__NSArrayM
2016-04-06 18:07:49.381 YGFSwizzling[6600:226036]__NSDictionaryI
2016-04-06 18:07:49.381 YGFSwizzling[6600:226036] __NSDictionaryM
例子:
//
// UIViewController+Extension.m
// YGFSwizzling
//
// Created byguangfu yang on 16/4/6.
// Copyright ©2016年 yangguangfu. All rights reserved.
//
#import <objc/runtime.h>
#import "UIViewController+Extension.h"
@implementation NSObject(Extension)
/**
* 交换两个对象方法
*
* @param class 类名称
* @param originSelector 原始方法名称
* @param otherSelector 交换的方法名称
*/
+ (void)swizzleInstanceMethod:(Class)classoriginSelector:(SEL)originSelectorotherSelector:(SEL)otherSelector{
Method otherMehtod = class_getInstanceMethod(class, otherSelector);
Method originMehtod = class_getInstanceMethod(class, originSelector);
// 交换2个方法的实现
if (class_addMethod(class, originSelector, method_getImplementation(otherMehtod), method_getTypeEncoding(originMehtod))) {
class_replaceMethod(class, otherSelector, method_getImplementation(originMehtod), method_getTypeEncoding(originMehtod));
} else {
method_exchangeImplementations(otherMehtod, originMehtod);
}
}
@end
@implementation UIViewController(Extension)
+ (void)load {
[self swizzleInstanceMethod:self originSelector:@selector(viewWillAppear:) otherSelector:@selector(mr_viewWillAppear:)];
}
/**
* 用来替换viewDidAppear,方便追踪
*
* @param animated 是否有动画
*/
- (void)mr_viewWillAppear:(BOOL)animated {
[self mr_viewWillAppear:YES];
NSLog(@"我是好人%@",self);
}
@end
@implementation NSArray(Extension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:objc_getClass("__NSArrayI") originSelector:@selector(objectAtIndex:)otherSelector:@selector(safe_objectAtIndex:)];
});
}
/**
* 用来替换取出数组的元素的方法
*
* @param index 数组下标
*
* @return 返回取出的值,如果越界返回nil
*/
- (instancetype)safe_objectAtIndex:(NSUInteger)index {
if (self.count - 1< index) {
@try {
return [self safe_objectAtIndex:index];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {
}
} else {
return [self safe_objectAtIndex:index];
}
}
@end
@implementation NSMutableArray (Extension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:objc_getClass("__NSArrayM") originSelector:@selector(objectAtIndex:)otherSelector:@selector(safe_objectAtIndex:)];
[self swizzleInstanceMethod:objc_getClass("__NSArrayM") originSelector:@selector(removeObjectAtIndex:)otherSelector:@selector(safe_removeObjectsAtIndex:)];
[self swizzleInstanceMethod:objc_getClass("__NSArrayM") originSelector:@selector(removeObject:inRange:)otherSelector:@selector(safe_removeObjectsInRange:)];
});
}
- (instancetype)safe_objectAtIndex:(NSUInteger)index {
if (self.count - 1< index) {
@try {
return [self safe_objectAtIndex:index];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {
}
} else {
return [self safe_objectAtIndex:index];
}
}
- (void)safe_removeObjectsAtIndex:(NSUInteger)index {
if (self.count - 1< index) {
@try {
return [self safe_removeObjectsAtIndex:index];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return ;
}
@finally {
}
} else {
return [self safe_removeObjectsAtIndex:index];
}
}
- (void)safe_removeObjectsInRange:(NSRange)range {
NSUInteger end = range.location + range.length - 1;
if (end > self.count - 1) {
@try {
[self safe_removeObjectsInRange:range];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
}
@finally {
}
} else {
[self safe_removeObjectsInRange:range];
}
}
@end
@implementation NSString (Extension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:objc_getClass("__NSCFConstantString") originSelector:@selector(substringToIndex:)otherSelector:@selector(safe_substringToIndex:)];
});
}
- (instancetype)safe_substringToIndex:(NSUInteger)index {
if (self.length - 1< index) {
@try {
return [self safe_substringToIndex:index];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {
}
} else {
return [self safe_substringToIndex:index];
}
}
@end
@implementation NSMutableString (Extension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:objc_getClass("__NSCFString") originSelector:@selector(substringToIndex:)otherSelector:@selector(safe_mutable_substringToIndex:)];
});
}
- (instancetype)safe_mutable_substringToIndex:(NSUInteger)index {
if (self.length - 1< index) {
@try {
return [self safe_mutable_substringToIndex:index];
}
@catch (NSException *exception) {
NSLog(@"---------- %sCrash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {
}
} else {
return [self safe_mutable_substringToIndex:index];
}
}
@end
注意事项
Swizzling通常被称作是一种黑魔法,容易产生不可预知的行为和无法预见的后果。虽然它不是最安全的,但如果遵从以下几点预防措施的话,还是比较安全的:
1 总是调用方法的原始实现(除非有更好的理由不这么做):API提供了一个输入与输出约定,但其内部实现是一个黑盒。Swizzle一个方法而不调用原始实现可能会打破私有状态底层操作,从而影响到程序的其它部分。
2 避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。
3 明白是怎么回事:简单地拷贝粘贴swizzle代码而不理解它是如何工作的,不仅危险,而且会浪费学习Objective-C运行时的机会。阅读Objective-C RuntimeReference和查看<objc/runtime.h>头文件以了解事件是如何发生的。
小心操作:无论我们对Foundation, UIKit或其它内建框架执行Swizzle操作抱有多大信心,需要知道在下一版本中许多事可能会不一样。
Method Swizzling危险吗?
既然MethodSwizzling
可以对这个类的Dispatch Table
进行操作,操作后的结果对所有当前类及子类都会产生影响,所以有人认为Method Swizzling
是一种危险的技术,用不好很容易导致一些不可预见的bug,这些bug一般都是非常难发现和调试的。
这个问题可以引用念茜大神的一句话:使用
Method Swizzling
编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全