美文网首页ios开发iOS Dev在路上程序员
通过Method Swizzling实现跨类Hook

通过Method Swizzling实现跨类Hook

作者: 6baf4fc957e2 | 来源:发表于2017-06-23 16:46 被阅读135次

    上篇文章描述了对同类/类别中通过Method Swizzling实现Hook的方案。而在日常实际工作中,场景远比预想要复杂的多。MVC的设计模式意味着view中并不掺杂model的逻辑,并且view与model原则上是没有任何牵连的,如果想通过model的变化去控制view更新,苹果给出的解决方案有委托、通知、设置API等等,由Controller统一去统筹控制。

    例如UITableView、UICollectionView等。
    这类控件的展示与数据源的绑定关系体现在了委托代理、数据源代理上。而当我们想要Hook这些回调或代理方法时,同类/类别的Method Swizzling就只能望洋兴叹了,因为要拦截的方法的实现位置,往往并不是自身。

    这时就体现出我们本章讨论内容的重要性了----跨类Method Swizzling。

    话不多说,先上个图:


    跨类Hook原理.png

    相信图片已经将原理描述的非常清楚了,如果再有看不明白的。。。。那就再看一遍~


    接下来是代码实现部分:
    抛砖引玉,期待各位大神更优雅的实现。

    //跨类交换方法
    + (void)exchangeImpWithOriginalClass:(Class)oriCls swizzledClass:(Class)swiCls originalSel:(SEL)oriSel swizzledSel:(SEL)swiSel tmpSel:(SEL)tmpSel msgForwardSel:(SEL)msgForwardSel
    {
        //增加原始方法
        Method originalMethod = class_getInstanceMethod(oriCls, oriSel);
        BOOL didAddOriMethod = class_addMethod(oriCls, oriSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        if (didAddOriMethod) {
            originalMethod = class_getInstanceMethod(oriCls, oriSel);
            SEL handleSel = msgForwardSel;
            IMP handleIMP = class_getMethodImplementation(self, handleSel);
            method_setImplementation(originalMethod, handleIMP);
        }
        
        //增加临时中转方法
        class_addMethod(oriCls, tmpSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        
        
        Method swizzledMethod = class_getInstanceMethod(swiCls, swiSel);
        class_replaceMethod(oriCls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(originalMethod));
        
    }
    

    代码对照着上图一起看,相信已经非常清晰了,不再赘述。
    不明白msgForwardSel的作用的,可以看下我的上篇文章

    接下来重点强调一下需要注意的点:
    1.当我们在swiIMP中插入Hook后需要操作的代码时,一定要注意,此时self为oriCls实例而并非当前Hook类的实例。所以使用一些方法时一定要注意,可能造成欺骗过了编译器而形成了运行时crash。
    2.在swiIMP中执行[self tmpSel]时,是安全的,这是由于我们创建tmpSel,是创建在了oriCls中,所以这里请放心使用。
    3.tmpSel方法的本质只是提供一个交换时的临时宿主,所以该方法实现中的代码永远不会执行,可以不写或随意写一个return nil即可。


    到了举例的阶段,一切的理论都要配合在实际场景中才显得更好理解:

    例如:我们需要在获取UITableView有多少组,并且打印出来。
    创建一个UITableView的类别,将下列代码粘贴进去

    + (void)load
    {
        //第一步:同类/类别交换setDataSource:方法。主要是用来获取tableView的DataSource实现实例
        [self exchangeImpWithClass:self originalSel:@selector(setDataSource:) swizzledSel:@selector(hook_setDataSource:)];
    }
    
    - (void)hook_setDataSource:(id<UITableViewDataSource>)dataSource
    {
        //第二步:通过跨类交换numberOfSectionsInTableView:方法,来监听方法触发动作
        [UITableView exchangeImpWithOriginalClass:[dataSource class] swizzledClass:[self class] originalSel:@selector(numberOfSectionsInTableView:) swizzledSel:@selector(hook_numberOfSectionsInTableView:) tmpSel:@selector(tmp_numberOfSectionsInTableView:) msgForwardSel:@selector(msgForward_numberOfSectionsInTableView:)];
        
        [self hook_setDataSource:dataSource];
    }
    
    - (NSInteger)hook_numberOfSectionsInTableView:(UITableView *)tableView
    {
        //插入逻辑代码
        NSInteger sec = [self tmp_numberOfSectionsInTableView:tableView];
        NSLog(@"拦截到的组数为:%ld", (long)sec);
        return sec;
    }
    
    - (NSInteger)tmp_numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 0;
    }
    
    - (void)msgForward_numberOfSectionsInTableView:(UITableView *)tableView
    {
        //如果原类中,未实现numberOfSectionsInTableView: 则此方法会执行
        NSLog(@"%s", __FUNCTION__);
    }
    
    
    #pragma mark - Swizzled
    + (void)exchangeImpWithClass:(Class)cls originalSel:(SEL)originalSel swizzledSel:(SEL)swizzledSel
    {
        Method originalMethod = class_getInstanceMethod(cls, originalSel);
        Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);
        
        BOOL didAddMethod = class_addMethod(cls,
                                            originalSel,
                                            method_getImplementation(originalMethod),
                                            method_getTypeEncoding(originalMethod));
        
        if (didAddMethod) {
            //如果add成功,说明原始类并没有实现此方法的imp,为避免调用时执行消息转发,此处做统一处理
            originalMethod = class_getInstanceMethod(cls, originalSel);
            SEL handleSel = @selector(msgForwardHandle);
            IMP handleIMP = class_getMethodImplementation(self, handleSel);
            method_setImplementation(originalMethod, handleIMP);
        }
        
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    
    //跨类交换方法
    + (void)exchangeImpWithOriginalClass:(Class)oriCls swizzledClass:(Class)swiCls originalSel:(SEL)oriSel swizzledSel:(SEL)swiSel tmpSel:(SEL)tmpSel msgForwardSel:(SEL)msgForwardSel
    {
        //增加原始方法
        Method originalMethod = class_getInstanceMethod(oriCls, oriSel);
        BOOL didAddOriMethod = class_addMethod(oriCls, oriSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        if (didAddOriMethod) {
            originalMethod = class_getInstanceMethod(oriCls, oriSel);
            SEL handleSel = msgForwardSel;
            IMP handleIMP = class_getMethodImplementation(self, handleSel);
            method_setImplementation(originalMethod, handleIMP);
        }
        
        //增加临时中转方法
        class_addMethod(oriCls, tmpSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        
        
        Method swizzledMethod = class_getInstanceMethod(swiCls, swiSel);
        class_replaceMethod(oriCls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(originalMethod));
        
    }
    
    - (void)msgForwardHandle
    {
        /**
         备用方法,防止原类中没有实现需要交换的方法,导致交换后执行消息转发最终没有处理导致crash。
         后续可以在做底层安全时,做到相应处理类中。
         */
        NSLog(@"%s  ", __FUNCTION__);
    }
    

    执行结果:

    2017-06-23 16:38:33.579 HookDemo[22159:4362685] 拦截到的组数为:5


    通过上述方案,可以实现非常强大的功能,例如通过类别方式实现UITableView/UICollectionView的刷新加载功能及自动显示隐藏空视图功能。
    如有时间,我会再后续文章中会写下该模块及开源相关组件代码。

    相关文章

      网友评论

        本文标题:通过Method Swizzling实现跨类Hook

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