非category的方式hook任意方法

1,279 阅读2分钟

前言

最近在写debug工具,需要hook一些方法,但是不希望debug工具依赖其他业务库,以前实现这种方式,是通过category,比如需要hook NSURLSessionConfigurationdefaultSessionConfiguration方法,用下面的方式实现,没毛病,一直都是这样搞的

@implementation NSURLSessionConfiguration (SKDTDebug)

+ (void)load {    
  Method method1 = class_getClassMethod([NSURLSessionConfiguration class], @selector(defaultSessionConfiguration));    
  Method method2 = class_getClassMethod([NSURLSessionConfiguration class], @selector(LL_defaultSessionConfiguration));    
  method_exchangeImplementations(method1, method2);
}

+ (NSURLSessionConfiguration *)LL_defaultSessionConfiguration {
  NSURLSessionConfiguration *config = [NSURLSessionConfiguration LL_defaultSessionConfiguration];    
  // ...
  return config;
}
@end

新方式

但是,因为是debug工具库,不能依赖其他工具或者业务库,比如我需要hook 网络库里SKIOTCPClientsendData的相关方法,不通过category的方式

#import <objc/runtime.h>
#import <objc/message.h>

@implementation SKDTTCPHook
+ (void)load{    
  Class SKIOTCPClientClass = NSClassFromString(@"SKIOTCPClient");    
  SEL debugSendDataContent = @selector(debugSendDataContent:writeBlock:);    
  SEL sendDataContent = @selector(sendDataContent:writeBlock:);    
  Method debugSendDataContentMethod_SKDTTCPHook = class_getInstanceMethod([SKDTTCPHook class], debugSendDataContent);    
  IMP debugSendDataContentImp_SKDTTCPHook = method_getImplementation(debugSendDataContentMethod_SKDTTCPHook);    
  char *debugSendDataContentTypes_SKDTTCPHook = (char *)method_getTypeEncoding(debugSendDataContentMethod_SKDTTCPHook);
  // 给SKIOTCPClient添加SKDTTCPHook的debugSendDataContent方法    
  BOOL success = class_addMethod(SKIOTCPClientClass, @selector(debugSendDataContent:writeBlock:), debugSendDataContentImp_SKDTTCPHook, debugSendDataContentTypes_SKDTTCPHook);    
  if (success) {        
    // 获取SKIOTCPClient里的debugSendDataContent方法        
    Method debugSendDataContentMethod_SKIOTCPClient = class_getInstanceMethod(SKIOTCPClientClass, debugSendDataContent);        
    Method originalMethod = class_getInstanceMethod(SKIOTCPClientClass, sendDataContent);   
    // 交换SKIOTCPClient里的两个方法        
    method_exchangeImplementations(originalMethod, debugSendDataContentMethod_SKIOTCPClient);        
    NSLog(@"exchange success");    
  }else{        
    NSLog(@"exchange failure, method already exist");    
  }
}
- (void)debugSendDataContent:(NSObject *)content writeBlock:(void(^)(NSInteger errorCode))writeBlock{    
  [self debugSendDataContent:content writeBlock:writeBlock];    
  // ...
}
@end

调用class_addMethod给需要hook的类手动添加你需要exchange的方法,就相当于实现了category,需要关注的是,添加了你自己的方法后,需要再次通过class_getInstanceMethod将你添加的方法从被hook的类里拿出来,再执行method_exchangeImplementations

抽取出来

+ (void)hookClass:(Class)originalClass originalSelector:(SEL)originalSelector toClass:(Class)swizzleClass swizzleSelector:(SEL)swizzleSelector{
    Method swizzleMethod = class_getInstanceMethod(swizzleClass, swizzleSelector);
    IMP swizzleImp = method_getImplementation(swizzleMethod);
    char *swizzleTypes = (char *)method_getTypeEncoding(swizzleMethod);
    // 将要exchange的方法添加到原始类中
    BOOL success = class_addMethod(originalClass, swizzleSelector, swizzleImp, swizzleTypes);
    if (success) {
        // 从原始类中重新读取出添加的方法
        Method swizzleMethod_original = class_getInstanceMethod(originalClass, swizzleSelector);
        Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
        // 交换原始类中两个方法实现
        method_exchangeImplementations(originalMethod, swizzleMethod_original);
        NSLog(@"exchange success");
    }else{
        NSLog(@"exchange failure, method already exist");
    }
}

调用

+ (void)load{
    Class SKIOTCPClientClass = NSClassFromString(@"SKIOTCPClient");
    SEL debugSendDataContent = @selector(debugSendDataContent:writeBlock:);
    SEL sendDataContent = @selector(sendDataContent:writeBlock:);
    [SKDTTCPHook hookClass:SKIOTCPClientClass originalSelector:sendDataContent toClass:[SKDTTCPHook class] swizzleSelector:debugSendDataContent];
}

- (void)debugSendDataContent:(NSObject *)content writeBlock:(void(^)(NSInteger errorCode))writeBlock{
    [self debugSendDataContent:content writeBlock:writeBlock];
    // ...
}

注意

用于exchange的方法里的self,指向的是被hook的类,所以无法通过self无法调用除自身之外的其他外部方法,除非你知道你的self都有哪些可以执行的方法,建议使用类方法,objc_msgSend或者NSNotification等方式进行转发