OCRuntime方法hook踩坑,hook具体类的方法

前提是想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后的方法实现:
fangfahook.png

到此就更加奇怪了,同样的代码,为什么用在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才能达到效果。

你可能感兴趣的:(OCRuntime方法hook踩坑,hook具体类的方法)