前提是想hook NSString类的某些实例方法,随便测试了两个方法,一个是原类中的方法:isEqual,另外一个是分类方法:isEqualToString,测试hook是否成功也很简单,分别调用一个字符串的这两个方法,看是否能打印出hook方法中的Log。
最初的hook代码如下:
@implementation NSString(isequal)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(isEqualToString:);
SEL swizzledSelector = @selector(zx_isEqualToString:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
originalSelector = @selector(isEqual:);
swizzledSelector = @selector(zx_isEqual:);
originalMethod = class_getInstanceMethod(class, originalSelector);
swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
success = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (BOOL)zx_isEqualToString:(NSString*)aString {
NSLog(@"调用了zx_isEqualToString");
return [self zx_isEqualToString:aString];
}
- (BOOL)zx_isEqual:(id)object{
NSLog(@"调用了zx_isEqual");
return [self zx_isEqual:object];
}
@end
测试代码:
NSString* strA = @"aaa";
NSString* strB = @"aaa";
if ([strA isEqualToString:strB]) {
NSLog(@"strOrg isEqualToString strCop!");
}
if ([strA isEqual:strB]) {
NSLog(@"strOrg isEqual strCop!");
}
这样看貌似一切都没有什么问题,run起来看打印结果,当调用[strOrg isEqualToString:strB]时,应当会调用hook方法,并打印“调用了zx_isEqualToString”这句话,当执行[strOrg isEqual:strB]时,应当打印hook方法中的“调用了zx_isEqual”这句话,但结果如何,实际run一下便知
但实际当执行[strA isEqualToString:strB]时,控制台并没有打印任何内容,执行[strOrg isEqual:strB]时,控制台也没有打印任何内容,这就奇怪了,难道hook代码有问题?方法并没有实现交换?
同样的hook代码,去hook UIView中的方法:
@implementation UIView(hook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(initWithFrame:);
SEL swizzledSelector = @selector(zx_initWithFrame:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
originalSelector = @selector(removeFromSuperview);
swizzledSelector = @selector(zx_removeFromSuperview);
originalMethod = class_getInstanceMethod(class, originalSelector);
swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
success = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (instancetype)zx_initWithFrame:(CGRect)frame {
//hook原类方法
NSLog(@"调用了hook 原类实例方法zx_initWithFrame");
return [self zx_initWithFrame:frame];
}
- (NSUInteger)zx_removeFromSuperview {
//hook分类方法
NSLog(@"调用了hook 分类实例方法zx_removeFromSuperview");
return [self zx_removeFromSuperview];
}
@end
测试代码很简单,分别执行initWithFrame和removeFromSuperview,最后查看打印结果:
UIView* view = [[UIView alloc] initWithFrame:CGRectZero];
[view removeFromSuperview];
实际run起来,查看控制台打印,和预期一致,分别调用了hook后的方法实现:
到此就更加奇怪了,同样的代码,为什么用在UIView的方法中,hook一点问题都没有,但是在NSString的方法中就不生效呢?问题到底在哪?
最终打断点,观察字符串有什么不同,在类型信息中显示
strA __NSCFConstantString * @"aaa" 0x000000010d4893a8
strB __NSCFConstantString * @"aaa" 0x000000010d4893c8
最关键的就是__NSCFConstantString,这个类型,说明strA,strB已经不是NSString,而是一个具体的String类型__NSCFConstantString,最终调整hook代码的Class class = [self class];这句,改成Class class = NSClassFromString(@"__NSCFConstantString");其他代码不变,只是把获取的类指向这个具体的类型。
再次打断点调试,所有hook方法都被正确调用,控制台如预期输出打印,结论就是当需要hook类似NSString这种类型的方法时要确定被调用的实例类型是准确的具体类型,否则hook的可能不是你所希望的类的实例方法。
类似的情况还有NSArray已及NSDictionary,他们对应的hook类型分别是__NSNSArrayI和__NSDictionaryI。
具体原因分析可能是NSString根据不同的实例化方法,会返回具体不同类型的String,例如__NSCFConstantString这种具体的String类型,类似于工厂,而hook需要针对具体类型进行hook才能达到效果。