iOS RSSwizzle中的swizzle原理

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_)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容