RSSwizzle
是一个简单的hook
函数的第三方库,它的使用跟传统的hook
方式比起来更加便捷,也更加安全。下面来分析它是怎么做到的。
传统的hook方法
实现
一般的,如果我们要viewDidLoad
,我们需要写如下的代码:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSel = @selector(viewDidLoad);
SEL swizzleSel = @selector(swizzle_viewDidLoad);
Method originalMethod =
class_getInstanceMethod([self class], originalSel);
Method swizzleMethod = class_getInstanceMethod([self class], swizzleSel);
1.
BOOL didAddMethod = class_addMethod(
[self class], originalSel, method_getImplementation(swizzleMethod),
method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
2.
class_replaceMethod([self class], swizzleSel,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
3.
method_exchangeImplementations(originalMethod, swizzleMethod);
}
});
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)swizzle_viewDidLoad {
[self swizzle_viewDidLoad];
NSLog(@"hook viewDidLoad");
}
如上所示,主要的逻辑都在load函数里面,其核心思路就是交换viewDidLoad
和`swizzle_viewDidLoad``的实现,下面简单解析一下:
- 尝试给当前类添加一个方法,该方法的方法名是
viewDidLoad
,实现是swizzle_viewDidLoad
的实现,这么做的目的是为了确保当前类一定有viewDidLoad
这个方法名(否则使用method_exchangeImplementations交换不会成功) - 添加成功,则将原来的
swizzle_viewDidLoad
的实现替换换成viewDidLoad的实现 - 如果添加不成功,则交换两个方法的实现
这样之后,只要viewDidLoad
被调用,则会走到swizzle_viewDidLoad
的实现上来,而swizzle_viewDidLoad
调用自己则走回原来的viewDidLoad
的实现,从而实现了hook
不足之处
这样写,大部分情况都是可以实现hook的,但是还是有一些边界情况没有考虑进去,比如originalSel在本类和父类都没有实现的情况,可以参考这篇文章。另外,没有一个hook就会要多写一个方法,写法上也不是很好。还有就是,hook的代码一旦不是写在load
函数里面(一般不会出现这种情况),则还要考虑多线程的问题。
RSSwizzle
RSSwizzle能规避上述的问题,如果要hook一个函数,不管在什么地方,只需要这么写:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RSSwizzleInstanceMethod([self class], @selector(viewDidLoad), RSSWReturnType(void), RSSWArguments(), RSSWReplacement({
RSSWCallOriginal();
NSLog(@"hook viewDidLoad");
}), RSSwizzleModeAlways, NULL)
});
其中RSSwizzleInstanceMethod
就是交换方法的宏,除了要传viewDidLoad之外,还要传入方法的返回参数和方法的参数,在block里面,就是替换的实现,其中RSSWCallOriginal
是另一个宏,就是调用原来的方法。
可以看出,这样调用比原来的方式要简洁多了。
RSSwizzle代码实现
在RSSwizzle.h文件中,定义了两个类,RSSwizzleInfo
用于保存原函数的实现,RSSwizzle
则是swizzle的主要类,其中有两个方法
+(BOOL)swizzleInstanceMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
mode:(RSSwizzleMode)mode
key:(const void *)key;
+(void)swizzleClassMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock;
看函数名可以知道,这个两个方法一个是针对类方法的swizzle一个是针对实例方法的swizzle。先看一下针对类方法的实现:
+(void)swizzleClassMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
{
[self swizzleInstanceMethod:selector
inClass:object_getClass(classToSwizzle)
newImpFactory:factoryBlock
mode:RSSwizzleModeAlways
key:NULL];
}
可以看出,其实最后还是调用swizzleInstanceMethod
,只是把该类对象的元类传进去而已。swizzleInstanceMethod
的代码如下:
+(BOOL)swizzleInstanceMethod:(SEL)selector
inClass:(Class)classToSwizzle
newImpFactory:(RSSwizzleImpFactoryBlock)factoryBlock
mode:(RSSwizzleMode)mode
key:(const void *)key
{
NSAssert(!(NULL == key && RSSwizzleModeAlways != mode),
@"Key may not be NULL if mode is not RSSwizzleModeAlways.");
@synchronized(swizzledClassesDictionary()){
if (key){
1.
NSSet *swizzledClasses = swizzledClassesForKey(key);
if (mode == RSSwizzleModeOncePerClass) {
if ([swizzledClasses containsObject:classToSwizzle]){
return NO;
}
}else if (mode == RSSwizzleModeOncePerClassAndSuperclasses){
for (Class currentClass = classToSwizzle;
nil != currentClass;
currentClass = class_getSuperclass(currentClass))
{
if ([swizzledClasses containsObject:currentClass]) {
return NO;
}
}
}
}
2.
swizzle(classToSwizzle, selector, factoryBlock);
if (key){
[swizzledClassesForKey(key) addObject:classToSwizzle];
}
}
return YES;
}
-
RSSwizzleMode
是一个枚举,用于决定这次hook是否能hook多次还是只能hook一次(或者是父类hook一次),它会根据key
对应的集合是否有当前要hook的类决定是否使用这次hook,通常在开发中mode会传RSSwizzleModeAlways
,key
会传NULL,因此代码会直接走到2这边来。PS感觉这个功能
比较鸡肋,如果别的模块使用传统的swizzle方法还是会hook住的,实在想不到应用场景。 - 这是swizzle的核心方法,
RSSwizzleImpFactoryBlock
的定义如下:
typedef id (^RSSwizzleImpFactoryBlock)(RSSwizzleInfo *swizzleInfo);
swizzle
方法的实现如下:
static void swizzle(Class classToSwizzle,
SEL selector,
RSSwizzleImpFactoryBlock factoryBlock)
{
1.
Method method = class_getInstanceMethod(classToSwizzle, selector);
NSCAssert(NULL != method,
@"Selector %@ not found in %@ methods of class %@.",
NSStringFromSelector(selector),
class_isMetaClass(classToSwizzle) ? @"class" : @"instance",
classToSwizzle);
2.
NSCAssert(blockIsAnImpFactoryBlock(factoryBlock),
@"Wrong type of implementation factory block.");
3.
__block OSSpinLock lock = OS_SPINLOCK_INIT;
__block IMP originalIMP = NULL;
4.
RSSWizzleImpProvider originalImpProvider = ^IMP{
OSSpinLockLock(&lock);
IMP imp = originalIMP;
OSSpinLockUnlock(&lock);
if (NULL == imp){
Class superclass = class_getSuperclass(classToSwizzle);
imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
}
return imp;
};
5.
RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
swizzleInfo.selector = selector;
swizzleInfo.impProviderBlock = originalImpProvider;
6.
id newIMPBlock = factoryBlock(swizzleInfo);
const char *methodType = method_getTypeEncoding(method);
NSCAssert(blockIsCompatibleWithMethodType(newIMPBlock,methodType),
@"Block returned from factory is not compatible with method type.");
IMP newIMP = imp_implementationWithBlock(newIMPBlock);
7.
OSSpinLockLock(&lock);
originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
OSSpinLockUnlock(&lock);
}
- 获取原方法的method
- 确保factoryBlock是否是RSSwizzleImpFactoryBlock,如果传的是
id(NSObject *obj){}
类型的block编译器不会报错,但在这里会进断言,其实就是判断二者的签名是否一致,这里就不展开讲了,有兴趣可以看这篇文章 -
originalIMP
是原来方法的实现,目前是NULL,后续会给它赋值,它可能是线程不安全的(因为引用它的block不知道会被哪个线程调用),因此需要加锁保护 -
originalImpProvider
就是返回一个原来方法的实现,如果本类没有,还会往父类那里找直到找到为止。这是因为7中的class_replaceMethod
只会返回本类的实现,不会再父类中查找 - 初始化
RSSwizzleInfo
,给它赋值 - 调用
factoryBlock
,拿到新的block,然后比较这个block跟原函数的函数签名是否一致,否则进断言,最后用这个block初始化newIMP
-
class_replaceMethod
替换原来的实现,如果本类没有这个方法,会默认加上这个替换的方法,返回的originalIMP
就是原来的实现
从6可以看出,传入的factoryBlock
的返回只能是一个block,否则这逻辑走不通,这个函数执行完之后,调用原来的函数,就执行了newIMP
此外,如果想在替换的方法里面调用原来的函数,我们就需要在RSSwizzleInfo
那里拿到原来的实现了,主要函数如下:
-(RSSwizzleOriginalIMP)getOriginalImplementation{
NSAssert(_impProviderBlock,nil);
return (RSSwizzleOriginalIMP)_impProviderBlock();
}
不难看出,其实就是调用4中的block取得IMP而已
如果只是通过swizzleInstanceMethod
这个函数来实现swizzle,那么我们必须要传对RSSwizzleImpFactoryBlock
,否则会进入断言,这样调用还是挺麻烦的,但是它提供的宏解决了这一问题。
RSSwizzle宏实现
使用RSSwizzle
进行hook的时候会使用到它的很多个宏,下面从它的参数开始说起:
RSSWReturnType
#define RSSWReturnType(type) type
这是一个返回值的宏,可以填写可以看出这个宏其实什么都没做,原样返回了,但是这样写增加了代码的易读性
RSSWArguments
#define RSSWArguments(arguments...) _RSSWArguments(arguments)
#define _RSSWArguments(arguments...) DEL, ##arguments
这是一个可以填多个参数的宏,...是多个参数的意思,arguments
是这些参数的集合。
##
在这里的意思是:如果没有arguments,则删掉##arguments和前面的逗号,也就是说,RSSWArguments()
最后得到的是DEL
,而所有的传参前面都会插入DEL
这个标志位,后续会移除这个标志位,这样做是为了规避没有参数的时候有时预编译会出现多余逗号的bug。
RSSWReplacement
#define RSSWReplacement(code...) code
这个宏封装替换的函数
RSSwizzleInstanceMethod
#define RSSwizzleInstanceMethod(classToSwizzle, \
selector, \
RSSWReturnType, \
RSSWArguments, \
RSSWReplacement, \
RSSwizzleMode, \
key) \
_RSSwizzleInstanceMethod(classToSwizzle, \
selector, \
RSSWReturnType, \
_RSSWWrapArg(RSSWArguments), \
_RSSWWrapArg(RSSWReplacement), \
RSSwizzleMode, \
key)
#define _RSSwizzleInstanceMethod(classToSwizzle, \
selector, \
RSSWReturnType, \
RSSWArguments, \
RSSWReplacement, \
RSSwizzleMode, \
KEY) \
[RSSwizzle \
swizzleInstanceMethod:selector \
inClass:[classToSwizzle class] \
newImpFactory:^id(RSSwizzleInfo *swizzleInfo) { \
RSSWReturnType (*originalImplementation_)(_RSSWDel3Arg(__unsafe_unretained id, \
SEL, \
RSSWArguments)); \
SEL selector_ = selector; \
return ^RSSWReturnType (_RSSWDel2Arg(__unsafe_unretained id self, \
RSSWArguments)) \
{ \
RSSWReplacement \
}; \
} \
mode:RSSwizzleMode \
key:KEY];
这个是最主要的宏,它封装了整个函数的调用,将其展开那就是:
[RSSwizzle
swizzleInstanceMethod:selector
inClass:[classToSwizzle class]
newImpFactory:^id(RSSwizzleInfo *swizzleInfo) {
1.
RSSWReturnType (*originalImplementation_)(_RSSWDel3Arg(__unsafe_unretained id,
SEL,
RSSWArguments));
2.
SEL selector_ = selector;
3.
return ^RSSWReturnType (_RSSWDel2Arg(__unsafe_unretained id self,
RSSWArguments))
{
RSSWReplacement
};
}
mode:RSSwizzleMode
key:KEY];
前两个参数都挺好懂,主要看最后一个参数:
1.根据函数参数和返回值声明一个名为originalImplementation_
的block,改block的返回值是RSSWReturnType
,也就是传入的返回值,参数值是id,SEL,和RSSWArguments
中传入的参数,_RSSWDel3Arg
的宏定义如下:
#define _RSSWDel3Arg(a1, a2, a3, args...) a1, a2, ##args
这是为了去掉第三个参数,前面说过RSSWArguments
会在参数前面插入一个DEL,就在这里去掉了
2.定义selector_
等于selector
,这是原函数的方法编号
3.定义了一个block作返回,这个block返回值是RSSWReturnType
,参数是self和RSSWArguments
中传入的参数,这里_RSSWDel2Arg
的意思跟_RSSWDel3Arg
类似,都是除掉多余的DEL,这个block的内容就是替换的函数,从上面的代码分析中我们知道,这个block就是用来初始化newIMP
的。
RSSWCallOriginal
在hook的时候很多时候都需要调用会之前的函数,这个时候就要调用RSSWCallOriginal
这个宏了,其定义如下:
#define RSSWCallOriginal(arguments...) _RSSWCallOriginal(arguments)
#define _RSSWCallOriginal(arguments...) \
((__typeof(originalImplementation_))[swizzleInfo \
getOriginalImplementation])(self, \
selector_, \
##arguments)
可以看出,它是在swizzleInfo
中拿到imp指针,然后将其强制转换为originalImplementation_
这个block进行调用,这样的好处由于originalImplementation_
的传参和返回值都由外界决定,因此如果传的不对编译器会报错,在一定程度上避免在运行期间进入断言。
有了这些宏之后,hook就变得方便多了
总结
RSSwizzle
虽然是个轻量易用的库,总共的代码量不多,但涉及到的知识还是挺多的,其中运行时和宏的相关的代码更是非常的精妙,推荐大家使用。