美文网首页
iOS RSSwizzle中的swizzle原理

iOS RSSwizzle中的swizzle原理

作者: 某某香肠 | 来源:发表于2019-08-25 20:58 被阅读0次

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``的实现,下面简单解析一下:

  1. 尝试给当前类添加一个方法,该方法的方法名是viewDidLoad,实现是swizzle_viewDidLoad的实现,这么做的目的是为了确保当前类一定有viewDidLoad这个方法名(否则使用method_exchangeImplementations交换不会成功)
  2. 添加成功,则将原来的swizzle_viewDidLoad的实现替换换成viewDidLoad的实现
  3. 如果添加不成功,则交换两个方法的实现

这样之后,只要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;
}
  1. RSSwizzleMode是一个枚举,用于决定这次hook是否能hook多次还是只能hook一次(或者是父类hook一次),它会根据key对应的集合是否有当前要hook的类决定是否使用这次hook,通常在开发中mode会传RSSwizzleModeAlwayskey会传NULL,因此代码会直接走到2这边来。PS感觉这个功能
    比较鸡肋,如果别的模块使用传统的swizzle方法还是会hook住的,实在想不到应用场景。
  2. 这是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);
}
  1. 获取原方法的method
  2. 确保factoryBlock是否是RSSwizzleImpFactoryBlock,如果传的是id(NSObject *obj){}类型的block编译器不会报错,但在这里会进断言,其实就是判断二者的签名是否一致,这里就不展开讲了,有兴趣可以看这篇文章
  3. originalIMP是原来方法的实现,目前是NULL,后续会给它赋值,它可能是线程不安全的(因为引用它的block不知道会被哪个线程调用),因此需要加锁保护
  4. originalImpProvider就是返回一个原来方法的实现,如果本类没有,还会往父类那里找直到找到为止。这是因为7中的class_replaceMethod只会返回本类的实现,不会再父类中查找
  5. 初始化RSSwizzleInfo,给它赋值
  6. 调用factoryBlock,拿到新的block,然后比较这个block跟原函数的函数签名是否一致,否则进断言,最后用这个block初始化newIMP
  7. 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虽然是个轻量易用的库,总共的代码量不多,但涉及到的知识还是挺多的,其中运行时和宏的相关的代码更是非常的精妙,推荐大家使用。

参考文献

Method Swizzle实际使用的坑

RSSwizzle源码解析

iOS 开发 高级:使用 宏定义macros (#,##,…,__VA_ARGS_)

相关文章

  • iOS RSSwizzle中的swizzle原理

    RSSwizzle是一个简单的hook函数的第三方库,它的使用跟传统的hook方式比起来更加便捷,也更加安全。下面...

  • iOS逆向之反HOOK的基本防护

    iOS逆向之Method Swizzle iOS逆向之fishHook原理探究 iOS逆向之fishHook怎么通...

  • 14-Hook原理(一)fishHook

    前言 本篇文章开始给大家分享下Hook(钩子)的原理,包括iOS系统原生的Method Swizzle,还有很有名...

  • Hook原理

    HOOK概述 HOOK示意图 iOS中HOOK技术的几种方式 Method Swizzle 2.fishhook ...

  • 《iOS 逆向》010-Hook简单使用

    iOS中HOOK技术的几种方式 1、Method Swizzle 利用OC的Runtime特性,动态改变SEL(方...

  • HOOK技术

    iOS中HOOK技术的几种方式 Method Swizzle利用OC的Runtime特性,动态改变SEL(方法编号...

  • iOS 中 copy 的原理

    iOS 中 copy 的原理iOS 中 copy 的原理

  • HOOK原理

    hook(钩子)处理特殊的消息机制 iOS中HOOK技术的几种方式 1、Method Swizzle利用OC的Ru...

  • iOS Method Swizzle 源码分析

    iOS Method Swizzle 代码 平常我们用的方法都是method_exchangeImplementa...

  • Swizzle in Objcet-C

    翻译自:the right way to swizzle in Object-C 在OC中,我们谈到swizzle...

网友评论

      本文标题:iOS RSSwizzle中的swizzle原理

      本文链接:https://www.haomeiwen.com/subject/dovkectx.html