iOS AOP框架Aspects实现原理

总结:

Aspects 是对 类的继承结构isa、mataclass结构的调整和维护;相当于链表的节点插入和删除;

同时使用method Swizzling 对方法统一重定向;

同时使用类似代理的机制对消息进行转发;

在类结构调整和消息交换重定向的过程中插入织入的功能。

 

前言

众所周知,Aspects框架运用了AOP(面向切面编程)的思想,这里解释下AOP的思想:AOP是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。也许大家用Aspects提供的两个方法用着很爽,有没有想揭开它的神秘面纱,看一下它的庐山真面目?接下来主要讲下Aspects的主要实现原理。


 
ruhua.gif

实现原理

其实主要涉及到两点,用过runtime的同学都知道swizzling和消息转发,如果还不是很清楚的同学可以看下我之前写的一篇文章OC中swizzling的“移魂大法”,“移魂大法”我这边就不累赘了,提下消息转发。

消息转发

 
MessageForward.jpg

当向一个对象发送消息的时候,在这个对象的类继承体系中没有找到该方法的时候,就会Crash掉,抛出* unrecognized selector sent to …异常信息,但是在抛出这个异常前其实还是可以拯救的,Crash之前会依次执行下面的方法:

  1. resolveInstanceMethod(或resolveClassMethod):实现该方法,可以通过class_addMethod添加方法,返回YES的话系统在运行时就会重新启动一次消息发送的过程,NO的话会继续执行下一个方法。

2.forwardingTargetForSelector:实现该方法可以将消息转发给其他对象,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。

  1. methodSignatureForSelector:会去获取一个方法签名,如果没有获取到的话就回直接挑用doesNotRecognizeSelector,如果能获取的话系统就会创建一个NSlnvocation传给forwardInvocation方法。

  2. forwardInvocation:该方法是上一个步传进来的NSlnvocation,然后调用NSlnvocationinvokeWithTarget方法,转发到对应的Target

  3. doesNotRecognizeSelector:抛出unrecognized selector sent to …异常。

整体原理

上面讲了几种消息转发的方法,Aspects主要是利用了forwardInvocation进行转发,Aspects其实利用和kvo类似的原理,通过动态创建子类的方式,把对应的对象isa指针指向创建的子类,然后把子类的forwardInvocationIMP替成__ASPECTS_ARE_BEING_CALLED__,假设要hook的方法名XX,在子类中添加一个Aspects_XX的方法,然后将Aspects_XXIMP指向原来的XX方法的IMP,这样方便后面调用原始的方法,再把要hook的方法XXIMP指向_objc_msgForward,这样就进入了消息转发流程,而forwardInvocationIMP被替换成了__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_addaspect_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对象以供调用。
  • AspectsContainer根据aliasSelector取出对象, 并组装一个AspectInfo, 带有原函数的调用参数和各项属性, 传给外部的调用者 (在这是block) .
  • 调用完成后销毁带有removeOptionhook逻辑, 将原selector挂钩到原IMP上, 删除别名selector

总结

-forwardInvocation:的消息转发虽然看起来很神奇,但是平时没什么需求的话尽量不要去碰这个东西,一般的话swizzling基本可以满足我们项目的大部分需求了,还有就是JSPatch既然不能用了,但是Aspects这个框架还是正常上AppStore的,利用这个库的话还是可以弄出轻量级Hotfix,感兴趣的同学可以点击轻量级低风险 iOS 热更新方案



作者:花了个缺
链接:https://www.jianshu.com/p/0d43db446c5b
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自www.cnblogs.com/feng9exe/p/10386486.html
今日推荐