交换已知类的实例方法:
- 给已知的类添加一个 category ,实现你想要hook的方法。
- 使用如下函数即可完成方法调换
+ (BOOL)swizzleClass:(Class)clazz sel:(SEL)origSEL withSel:(SEL)swizzlingSEL {
Method orgMethod = class_getInstanceMethod(clazz, origSEL);
Method swizzlingMethod = class_getInstanceMethod(clazz, swizzlingSEL);
if (!orgMethod || !swizzlingMethod) {
return NO;
}
BOOL added = class_addMethod(clazz, origSEL,
method_getImplementation(swizzlingMethod),
method_getTypeEncoding(swizzlingMethod));
if (added) {
// 返回成功,说明origSEL只是基类的实现,并且已经在当前类添加了实现,实现指向的是swizzlingMethod。
// 这时,只需要把swizzlingSEL指向原实现即可完成交换
class_replaceMethod(clazz, swizzlingSEL,
method_getImplementation(orgMethod),
method_getTypeEncoding(orgMethod));
} else {
// 返回失败,说明origSEL已经在当前类实现了,直接交换即可。
method_exchangeImplementations(orgMethod, swizzlingMethod);
}
return YES;
}
说明:为什么不能直接method_exchangeImplementations 呢?
因为,如果是基类实现的原方法,那么,直接交换的话,会导致某个基类的实现被交换了,其他只用到基类(而不是当前交换类)的方法调用也会进入我们的交换方法里了。
举例
我们想要观察一些微信SDK打开微信的时候传递了什么参数,我们就可以通过调换UIApplication的openURL方法,增加日志来实现
@interface UIApplication (SwizzeTest)
@end
@implementation UIApplication (SwizzeTest)
- (BOOL)my_openURL:(NSURL *)url
{
NSLog(@"openURL:%@", url);
return [self my_openURL:url];
}
@end
[HooTools swizzleClass:[UIApplication class] sel:@selector(openURL:) withSel:@selector(my_openURL:)];
交换类的方法
交换类的方法和交换类的实例方法的原理是一样的,只是操作的对象不是Class而是Meta Class。 具体的代码如下
+ (void)swizzleClassMethodForClass:(Class)className sel:(SEL)orgSel withSel:(SEL)swizzlingSel
{
// 交换实例方法
Method originalMethod = class_getClassMethod(className, orgSel);
Method swizzledMethod = class_getClassMethod(className, swizzlingSel);
if (!originalMethod || !swizzledMethod) {
return;
}
Class metaClass = object_getClass(className);
BOOL added = class_addMethod(metaClass, orgSel,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (added) {
class_replaceMethod(metaClass, swizzlingSel,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
交换未知类的方法
我们在跟踪一些问题的时候,经常会发现OC内部其实有很多内部实现的类型,比如NSString *str = @"aa"; str对象内部的实际类型是__NSCFConstantString。
如果我们直接去交换NSString的方法,是达不到我们想要的目的的。
这时候,需要用改进一下上面的交换方法的实现,让它能够支持替换成完全另一个类的方法。
实现如下:
+ (BOOL)swizzleClassNamed:(NSString *)orgClassName
methodNamed:(NSString *)orgMethodName
withClass:(Class)swizzlingClass
andSelector:(SEL)swizzlingSEL
{
Class orgClass = NSClassFromString(orgClassName);
SEL orgSEL = NSSelectorFromString(orgMethodName);
Method orgMethod = class_getInstanceMethod(orgClass, orgSEL);
Method swizzlingMethod = class_getInstanceMethod(swizzlingClass, swizzlingSEL);
if (!orgMethod || !swizzlingMethod) {
return NO;
}
BOOL added = class_addMethod(orgClass, swizzlingSEL, method_getImplementation(orgMethod), method_getTypeEncoding(orgMethod));
// 这里肯定是要成功的,否则就是存在同名的方法了,需要改名处理一下
if (!added) {
return NO;
}
added = class_addMethod(orgClass, orgSEL, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
if (!added) {
// 不成功,说明当前类已经有实现了原方法的,直接对调此方法和交换方法即可。
method_exchangeImplementations(orgMethod, swizzlingMethod);
}
// 如果是成功的话,就是完成了向当前类添加原方法,实现指向交换方法的实现。
// 配合前面addMethod的结果,当前类添加了交换方法,实现指向原方法的实现。
return YES;
}
举例
假如,我们遇到了一个第三方SDK使用 NSURLComponents 的 setPercentEncodedQuery 方法时候产生的崩溃问题,我们希望看看到底是什么参数设置导致的崩溃。
从崩溃的日志看,其内部类型是__NSConcreteURLComponents,通过上述方法在自己的类实现一个swizzling_setPercentEncodedQuery,并hook到我们实现的这个方法:
- (void)swizzling_setPercentEncodedQuery:(NSString *)query
{
NSLog(@"swizzling_setPercentEncodedQuery:%@", query);
[self swizzling_setPercentEncodedQuery:query];
}
[HooTools swizzleClassNamed:@"__NSConcreteURLComponents" methodNamed:@"setPercentEncodedQuery:" withClass:[self class] andSelector:@selector(swizzling_setPercentEncodedQuery:)];
补充
Github上有一些功能更加完毕的开源库
- github.com/rentzsch/jr…
- github.com/rabovik/RSS… 都是一些多年都没怎么改动过的库了,技术应该也是相对成熟的。