iOS AOP框架Aspects实现原理
总结:
Aspects 是对 类的继承结构isa、mataclass结构的调整和维护;相当于链表的节点插入和删除;
同时使用method Swizzling 对方法统一重定向;
同时使用类似代理的机制对消息进行转发;
在类结构调整和消息交换重定向的过程中插入织入的功能。
前言
众所周知,Aspects框架运用了AOP(面向切面编程)的思想,这里解释下AOP的思想:AOP是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。也许大家用Aspects提供的两个方法用着很爽,有没有想揭开它的神秘面纱,看一下它的庐山真面目?接下来主要讲下Aspects的主要实现原理。
实现原理
其实主要涉及到两点,用过runtime的同学都知道swizzling
和消息转发,如果还不是很清楚的同学可以看下我之前写的一篇文章OC中swizzling的“移魂大法”,“移魂大法”我这边就不累赘了,提下消息转发。
消息转发
当向一个对象发送消息的时候,在这个对象的类继承体系中没有找到该方法的时候,就会Crash掉,抛出* unrecognized selector sent to …
异常信息,但是在抛出这个异常前其实还是可以拯救的,Crash之前会依次执行下面的方法:
resolveInstanceMethod
(或resolveClassMethod
):实现该方法,可以通过class_addMethod
添加方法,返回YES的话系统在运行时就会重新启动一次消息发送的过程,NO的话会继续执行下一个方法。
2.forwardingTargetForSelector
:实现该方法可以将消息转发给其他对象,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。
methodSignatureForSelector
:会去获取一个方法签名,如果没有获取到的话就回直接挑用doesNotRecognizeSelector
,如果能获取的话系统就会创建一个NSlnvocation
传给forwardInvocation
方法。forwardInvocation
:该方法是上一个步传进来的NSlnvocation
,然后调用NSlnvocation
的invokeWithTarget
方法,转发到对应的Target
。doesNotRecognizeSelector
:抛出unrecognized selector sent to …
异常。
整体原理
上面讲了几种消息转发的方法,Aspects
主要是利用了forwardInvocation
进行转发,Aspects
其实利用和kvo
类似的原理,通过动态创建子类的方式,把对应的对象isa指针指向创建的子类,然后把子类的forwardInvocation
的IMP
替成__ASPECTS_ARE_BEING_CALLED__
,假设要hook
的方法名XX
,在子类中添加一个Aspects_XX
的方法,然后将Aspects_XX
的IMP
指向原来的XX
方法的IMP
,这样方便后面调用原始的方法,再把要hook
的方法XX
的IMP
指向_objc_msgForward
,这样就进入了消息转发流程,而forwardInvocation
的IMP
被替换成了__ASPECTS_ARE_BEING_CALLED__
,这样就会进入__ASPECTS_ARE_BEING_CALLED__
进行拦截处理,这样整个流程大概结束。
代码解析
代码解析这块的话主要讲下核心的模块,一些边边角角的东西去看了自然就明白了。
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default)
AspectPositionInstead = 1, /// Will replace the original implementation.
AspectPositionBefore = 2, /// Called before the original implementation.
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
AspectOptions
提供各种姿势,前插、后插、整个方法替换都行,简直就是爽。
static void aspect_performLocked(dispatch_block_t block) {
static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&aspect_lock);
block();
OSSpinLockUnlock(&aspect_lock);
}
在aspect_add
、aspect_remove
方法里面用了aspect_performLocked
, 而aspect_performLocked
方法用了OSSpinLockLock
加锁机制,保证线程安全并且性能高。不过这种锁已经不在安全,主要原因发生在低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量 CPU 时间,从而导致低优先级线程拿不到 CPU 时间,也就无法完成任务并释放锁。这种问题被称为优先级反转,有兴趣的可以点击任意门不再安全的 OSSpinLock
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
aspect_swizzleForwardInvocation(subclass);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
aspect_hookClass
这个方法顾名思义就是要hook对应的Class,这在里创建子类,通过aspect_swizzleForwardInvocation
方法把forwardInvocation
的实现替换成__ASPECTS_ARE_BEING_CALLED__
, aspect_hookedGetClass
修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回当前对象的 class,最后通过object_setClass
把isa指针指向subclass
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
Class klass = aspect_hookClass(self, error);
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
// We use forwardInvocation to hook in.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
通过aspect_isMsgForwardIMP
方法判断要hook的方法时候被hook了,这里如果有使用JSPatch的话会冲突掉,不过JSPatch已经被苹果禁止使用了,苹果爸爸说的算。如果没有被hook走的话会先创建一个有自己别名的方法,然后把这个带有别名方法的IMP指向原始要hook方法的实现,最后要hook的方法的IMP指向_objc_msgForward
或者_objc_msgForward_stret
,为什么还会有一个_objc_msgForward_stret
,详细原因可以参考JSPatch实现原理详解<二>里面的Special Struct
// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
invocation.selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks.
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
// Remove any hooks that are queued for deregistration.
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
该函数为swizzle
后, 实现新IMP
统一处理的核心方法 , 完成一下几件事:
- 处理调用逻辑, 有before, instead, after, remove四种option
- 将block转换成一个
NSInvocation
对象以供调用。 - 从
AspectsContaine
r根据aliasSelector
取出对象, 并组装一个AspectInfo
, 带有原函数的调用参数和各项属性, 传给外部的调用者 (在这是block
) . - 调用完成后销毁带有
removeOption
的hook
逻辑, 将原selecto
r挂钩到原IMP
上, 删除别名selector
总结
-forwardInvocation:
的消息转发虽然看起来很神奇,但是平时没什么需求的话尽量不要去碰这个东西,一般的话swizzling
基本可以满足我们项目的大部分需求了,还有就是JSPatch
既然不能用了,但是Aspects这个框架还是正常上AppStore的,利用这个库的话还是可以弄出轻量级Hotfix
,感兴趣的同学可以点击轻量级低风险 iOS 热更新方案
作者:花了个缺
链接:https://www.jianshu.com/p/0d43db446c5b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
iOS AOP框架Aspects实现原理的更多相关文章
- JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架
1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...
- AOP框架Dora.Interception 3.0 [2]: 实现原理
和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链.Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们 ...
- 全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理
本系列前面的五篇文章主要介绍Dora.Interception(github地址,觉得不错不妨给一颗星)的编程模式以及对它的扩展定制,现在我们来聊聊它的设计和实现原理.(拙著<ASP.NET C ...
- SSH三大框架的工作原理及流程
Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.x ...
- [Java] SSH框架笔记_SSH三大框架的工作原理及流程
Hibernate工作原理及为什么要用? 原理:1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件2.由hibernate.cfg.x ...
- [转]iOS hybrid App 的实现原理及性能监测
转自:http://www.cocoachina.com/ios/20151118/14270.html iOS hybrid App 的实现原理及性能监测 2015-11-18 11:39 编辑: ...
- Spring框架和MVC原理
Spring框架和MVC原理 目录 Spring框架 SpringMVC工作原理 参考资料 回到顶部 Spring框架 Spring当前框架有20个jar包,大致可以分为6大模块: Core Cont ...
- iOS绘图框架CoreGraphics分析
由于CoreGraphics框架有太多的API,对于初次接触或者对该框架不是十分了解的人,在绘图时,对API的选择会感到有些迷茫,甚至会觉得iOS的图形绘制有些繁琐.因此,本文主要介绍一下iOS的绘图 ...
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架
类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 类加载器也是Jav ...
随机推荐
- .net core 2.2 部署CentOS7(5)部署.net core mvc
目录: .net core 2.2 部署CentOS7(1)安装虚拟机 .net core 2.2 部署CentOS7(2)给虚拟机安装CentOS7 .net core 2.2 部署CentOS7( ...
- ipV4&V6的区别
1. 更大的地址空间.IPv4中规定IP地址长度为32,即有2^32-1个地址:而IPv6中IP地址的长度为128,即有2^128-1个地址. 2. 更小的路由表.IPv6的地址分配一开始就遵循聚类( ...
- 关于PLSQL启动用时较长的问题解决
问题: 打开登陆界面缓慢. 解决: 1.删除控制面板中的打印机 2.将打印机改为手动并停止启动状态 .
- 《码出高效 Java开发手册》第六章 数据结构与集合
码云: https://gitee.com/forxiaoming/JavaBaseCode/blob/master/EasyCoding/src/collection/index.md 6.1 数据 ...
- linux 用vi命令的使用以及vi编辑后的后续保存退出等相关命令的使用
一.首先用vi命令打卡要编辑的文件: 注意:vi命令的使用如下 打开或新建文件,并将光标至于第一行首:[root@centos6 /]# vi /etc/my.cnf 打开文件,并将光标移至最后一行行 ...
- 洛谷P2973 [USACO10HOL]赶小猪(高斯消元 期望)
题意 题目链接 Sol 设\(f[i]\)表示炸弹到达\(i\)这个点的概率,转移的时候考虑从哪个点转移而来 \(f[i] = \sum_{\frac{f(j) * (1 - \frac{p}{q}) ...
- bootstrap前端框架使用总结分享
1.bootstrap 排版 全局样式style.css: 1.移除body的margin声明 2.设置body的背景色为白色 3.为排版设置了基本的字体.字号和行高 4.设置全局链接颜色,且当链接处 ...
- opencv3.2.0图像处理之中值滤波medianBlur API函数
/*中值滤波:medianBlur函数是非线性滤波 函数原型:void medianBlur(inputArray src,OutputArray dst,int ksize) 参数详解: input ...
- Solo 博客系统 1.7.0 发布 - 新版 MD 编辑器
本次发布主要是更新了 Markdown 编辑器,加入了全屏.Emoji 自动完成.粘贴时自动转换为 MD 格式的特性.(1.7.0 版本变更记录请看这里) 目前的 Markdown 编辑器有多好用呢? ...
- 构建微服务:快速搭建Spring Boot项目
Spring Boot简介: Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员 ...