先看几个概念:
代理:每个业务类都存在核心代码(当且业务独有的逻辑)和非核心代码(每个业务都可能有的通用逻辑)。目标类只完成核心业务,将非核心代码交给代理类完成具体实现。使核心逻辑更加简介、清晰。目标类和代理类要在编译期确定绑定关系。
动态代理:代理类完成通用非核心业务,在运行期动态绑定目标类。不需要修改核心代码,避免一个类对应一个代理,统一代理类对目标类的处理过程,减少重复代码。例如给一些类加统一的权限控制;日志系统;监控系统等都可以用动态代理实现。
AOP(Aspect Oriented Programming):面向切面编程,在不修改核心代码的情况下运行期给原程序添加额外的功能,使核心业务逻辑的附加功能完全隔离开,降低模块耦合度。动态AOP一般用动态代理的思想实现。如性能监测;访问控制;事务管理以及日志记录;线上热修复工具等,都参考了AOP设计。
isa:运行时指向当前对象的类对象。
Class:一个类也是一个对象,描述了一个类的所有实例方法、协议和属性。
meta-class:类对象的类,描述了一个类的所有类方法。class.png
method:一个包含了方法名、参数和返回值类型、imp的结构体。
imp:方法的真正实现(函数指针)。
type encoding:用字符串描述OC所有的数据类型。
selector:OC方法名(包括参数名和参数个数)。
NSMethodSignature:方法签名,记录了一个方法的返回值类型类型和参数。用于创建NSInvocation 。
NSInvocation:OC方法的静态描述,Invocation对象包含了OC方法所有的参数和返回值,用于消息转发(Invocation可以分发到其他target,所有参数都可以修,selector也可以转成另外一个具有相同方法签名的selector)。
方法调用:其实是对objc_msgSend(id self, SEL op, ...)的函数调用,根据self和SEL,先在当前class的方法缓存cache中匹配SEL,再从class的methodLists中匹配,没有找到再去父类中找,直到NSObject,如果都没有找到imp,则走消息转发流程。oc_message.png
消息转发:动态方法解析(动态添加一个方法来响应当前的消息)、快速消息转发(将该条消息发送给其他接收者来处理)、标准消息转发(不仅可以更改消息接受者,还可以通过invocaton更该方法参数和返回值,甚至修改SEL)、抛出异常。msg_forawrd.png
OC中所有具有isa和super_class指针的结构体都是一个对象,类本身也是一个对象,可以在运行时动态创建类对象。我们在运行时能够动态更改一个实例的行为,给他添加新方法或者修改原有方法,甚至修改类的指向,其实都是修改了类所指向的对象,或者修改了类的实例的isa指针,使他指向了其它的类对象。
Aspects正是利用了OC的这个特性,给我们提供了两个接口
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
第一个类方法:可以动态修改原有类中定义好的方法实,当被修改的类生成实例之后,就具有新的行为,实现了AOP设计。
第二个实例方法:可以在类生成实例之后继承之前的类动态创建一个子类,并且修改子类中的方法,创建子类对象,再将原来类实例的isa指向子类对象,通过block将原类的invocation和实参回调给用户。可以实现动态修改一个对象的行为。
以上两个接口的区别是:类方法直接修改了原来的类,该类生成的所有实例都将被修改。第二个实例方法只修改了这个类当前的实例,不会对类其它实例造成影响。
Aspects实现流程
1、 Check against the blacklist
1) 下面这些方法不会hook。
@"retain"
@"release",
@"autorelease"
@"forwardInvocation:"
2) 对@"dealloc"进行hook只能使用
AspectPositionBefore
3) 不存在的方法不会hook。
如果是meta class还会执行4) 5)
如果hook的是instance执行跳到第2步
4) 便利当前类和父类直到NSObject
定位到需要hook的方法所在的类,在
缓存(swizzledClassesDict)中查该
方法是否已经被hook。一个类中的一
个方法,只能被hook一次。
5) 再次便利当前类和父类
将需要hook的方法做标记
存在缓存(swizzledClassesDict)中。
ps:4、5两步是否存在性能问题?
2、 生成AspectsContainer
1) 给原方法起别名(加aspects__前缀)
生成AspectsContainer实例。
2) 以别名为key,将生成AspectsContainer
作为一个属性添加给当前类。
3、 生成blockSignature
1) 解析调用者传进来的block
(block的结构比较复杂,要另外讨论)
2) 如果调用者没有传入id则不需要生成方法签名
3) 如果传入了id,则生成方block法签名。
4) 根据原方法的@selector生成方法签名signature
并且和blockSignature做匹配
a.对比参数个数
b.只校验原方法的signature和blockSignature
参数类型和个数是否一致。
c.前2个参数不一样,分别是:
argument 0(@ 代表self或者block)
argument 1 (: 代表selector)。
5) 校验结束,根据原方法
selector,block,blockSignature,
options,self(类或者实例,弱引用)
生成AspectIdentifier可用于取消hook。
6) 将生成好的AspectIndetifer对象
加入第2步生成的AspectsContainer对象中
4、 修改原类,对其hook的方法进行消息转发拦截
1) 准备hook原类(如果是调用的是类方法则做消息转发拦截,如果是实例方法会动态生成子类)
a.检测原class是否已经存在_Aspects_前缀
有的话直接返回原来的class
b.判断是hook原类还是hook类的实例
1. hook meta_class
a) 将原类forwardInvocation方法IPM
替换成__ASPECTS_ARE_BEING_CALLED__
b) 如果class_replaceMethod返回原IMP
在添加一个__aspects_forwardInvocation方法
方法IMP就是原来forwardInvocation的IMP
作用是调用unregister的时候在把原来的forwardInvocation方法替换回来
但这里测试下来发现class_replaceMethod没有返回原IMP?
c) !!!运行时(其它都是非运行时对class的操作):
1)__aspects_forwardInvocation用原方法的invocation
将invocation的实参替换blockInvocation的参数
2).将原方法Invocation、当前self、方法参数列表
包装成id<AspectInfo>对象
塞给blockInvocation的第一个参数
调用者可以通过AspectInfo在block中取到原方法的Invocation和参数
3)方法执行,三种模式,调用顺序:
AspectPositionBefore:执行block、执行原方法invocation。
AspectPositionAfter: 执行原方法invocation,执行block。
AspectPositionInstead:执行block、要手动执行原方法的invocation。
4) 执行过程中如果aspectsContainer中找不到需要替换的方法
调__aspects_forwardInvocation(IMP:原forwardInvocation)走消息转发
forwardInvocation无法响应则掉用doesNotRecognizeSelector
!!!b)流程class_replaceMethod的一个bug,它没有返回
原forwardInvocation方法的IMP导致__aspects_forwardInvocation的
IMP为空,因此一定会走doesNotRecognizeSelector。
ps:如果使用了像《JSPatch》这种也将hook的方法换行成forwardInvocation
走到这里就会Crash。
2. hook instance
a) 根据原类,创建一个名为(originalClassName__Aspects_)的新class和metaclass,并创建类对象
b) 将新类forwardInvocation方法IPM替换成__ASPECTS_ARE_BEING_CALLED__。
c) 替换新类的class方法,新IMP是一个"Class (^block)(id self)";
这个block返回原类的Class;
替换后新类的实例调用getClass方法时返回的是原类的Class;
因为self虽然赋值给了新生成的Class;
但是self的MethodList中记录的Class,SEL,IMP对应关系没变;
要通过class_getInstanceMethod获取SEL的Method;
或者instanceMethodSignatureForSelector获取方法MethodSignature
还需要用原Class。
d) 替换新类的class方法,同:c)。
e) 注册新class;
调用object_setClass方法将当前self实例赋值给成新生成的类。;
此时新的class的实例已经变成self;
self的内存地址没变只是类的结构变成了新生成的Class。
f) !!!运行时(其它都是非运行时对class的操作):
同meta_class方法
2) 在全局集合swizzledClasses中加入原类名,做缓存使用。并且返回上一步生成的class。
3) 根据上一步返回的class,获取原方法的IMP。
4) 判断上一步的IMP是否是_objc_msgForward
如果是则,调用原方法就是直接会走消息转发流程,如果不是会进行下一步
5) 给class新增一个aspects_selector指向原IMP(unregister时候使用)。
6) 将原IMP替换成_objc_msgForward(作用是:调用原方法直接进行消息转发)。