runtime相关iOS综合相关iOS学习笔记

Method Swizzle

2016-07-09  本文已影响206人  子瑜愚

1、AOP编程思想

1.1、AOP是什么

AOP(Aspect Oriented Programming)直译为面向切面编程。假设把应用程序想象成一个长方体,OOP就是纵向划分系统,把系统分成很多个业务模块,如用户管理,产品展示等模块;AOP横向划分系统,提取各个业务模块可能要重复操作的部分。
AOP编程的原理是在不更改正常的业务处理流程的前提下,通过生成一个动态代理类,从而实现对目标对象嵌入附加的操作。AOP是OOP的一个补充。

1.2、AOP的使用场景

AOP最常见的使用场景:日志记录、性能统计、安全控制、事务处理、异常处理、调试等。这些事务琐碎,跟主要业务逻辑无关,在很多地方都有,又很难抽象出来单独的模块。

比如,最常见的用户习惯统计。最简单的思路是:在viewDidAppearviewDidDisappear两个方法中,分别调用对应的第三方接口来实现页面驻留时长统计。
但是这部分代码并不涉及页面逻辑,而且app中的统计事件代码会散布在各个viewcontroller中,你可能会忘记自己添加的统计事件,也会不利于统计相关的修改。
这时候,我们就会想把类似通用的代码从业务逻辑中分离出去,整合到一块。

2、Method Swizzling

2.1、Method Swizzling是什么

在iOS中实现AOP编程思想的一种方式是Method SwizzlingMethod Swizzling利用Runtime特性把一个方法的实现和另一个方法的实现进行替换,在程序运行时修改Dispatch TableSELIMP的映射关系。
通过 swizzling method 改变目标函数的 selector 所指向的实现,然后在新的实现中实现附加的操作,完成之后再调用原来的处理逻辑。

2.2、Method Swizzling的优势

2.3、实现方案

3、常见的实现

3.1、普通类的swizzling

关键代码与注释:

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(testMethod));
        Method swizzleMethod = class_getInstanceMethod([self class
                                                        ],@selector(swizzle_testMethod));
/**************第一种替换方式**********/
        // 如果当前类没有实现名为originalSelector的方法,originalMethod如果不为空,拿到的则是其父类的名为originalSelector的方法的IMP,这里先给当前类新增originalSelector的IMP,对应的IMP是swizzleMethod中的IMP
        BOOL didAddMethod = class_addMethod([self class], @selector(testMethod), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        if (didAddMethod) {// 如果当前类已经有名为originalSelector的实现,则返回NO
            // 将swizzleSelector的IMP替换成父类originalSelector的IMP。
            class_replaceMethod([self class], @selector(swizzle_testMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            // 如果当前类都有实现待替换的两个方法,直接交换
          //   这两个参数没有先后顺序
            method_exchangeImplementations(originalMethod, swizzleMethod);
        }
 /**************第二种替换方式,有问题,不要用**********/
        // class_replaceMethod这个方法的效果:
        // 如果在当前类有对应的实现,执行method_setImplementation
        // 如果当前类没有对应的实现,执行class_addMethod
        // 这儿的先后顺序是有讲究的,应该优先把新的方法指定原IMP,再修改原有的方法的IMP
        // 如果先执行原方法指向新的IMP,那么在执行完瞬间方法被调用容易引发死循环,这样的顺序至少保证如果没有hook成功,也只是调用原来的实现,不会出现死循环
/////error///////// 这段代码有问题,hook自己写的testMethod,无法输出附加的代码
/////error/////////  而且如果父类同时也写了一样的swizzle的代码,hook会被抵消
//        class_replaceMethod([self class], @selector(swizzle_testMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
//        class_replaceMethod([self class], @selector(testMethod), method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    });
}

3.2、类簇类的swizzling

在Objective-C中有一些类叫类簇,比如:NSNumberNSArrayNSMutableArray等。
何为类簇,类簇其实是抽象工厂模式的实现,比如NSArray并不是一个具体类,其实它是一个抽象类,它对外提供了统一访问的接口,它的真实的类型会根据具体情况创建。
所以,我们在给类簇类做method swizzle并不能用[self class]获得其真实的类名。
比如,我们可以通过以下代码来得到NSMutableArray的真实类型:

object_getClass([[NSMutableArray alloc] init]);
objc_getClass("__NSArrayM");

上面代码中__NSArrayM是NSMutableArray的真实类名;
对应的一些常见类簇的真实类名:

真实类名
NSArray __NSArrayI
NSMutableArray __NSArrayM
NSDictionary __NSDictionary
NSMutableDictionaary __NSDictionaryM

3.3、为什么在+load()方法中实现swizzling

3.4、为什么swizzling的实现要放在dispatch_once

什么情况下,+load会被调用两次?

3.5、第三方jrswizzle

4、使用消息转发实现(分析Aspect源码)

4.1、Aspect源码的实现思路

对于待 hook 的 selector,将其指向 objc_msgForward / _objc_msgForward_stret;同时生成一个新的 aliasSelector 指向原来的 IMP,并且 hook 住 forwardInvocation 函数,使他指向自己的实现。

当被 hook 的 selector 被执行的时候,首先根据 selector 找到了 objc_msgForward / _objc_msgForward_stret ,而这个会触发消息转发,从而进入 forwardInvocation。同时由于 forwardInvocation 的指向也被修改了,因此会转入新的 forwardInvocation 函数,在里面执行需要嵌入的附加代码,完成之后,再转回原来的 IMP。

Aspect源码的关键流程:

4.2、Aspect源码的简单介绍


上一篇 下一篇

猜你喜欢

热点阅读