iOS 开源库系列 Aspects核心源码分析---面向切面编程之疯狂的 Aspects


Aspects的源码学习,我学到的有几下几点

  1. Objective-C Runtime
  2. 理解OC的消息分发机制
  3. KVO中的指针交换技术
  4. Block 在内存中的数据结构
  5. const 的修饰区别
  6. block 中常量在特定情况下的三种处理方法
  7. 断言语句,
  8. 自旋锁 使用注意
  9. _objc_msgForward_stret 和 _objc_msgForward 前者存在的必要

  10. Type Encoding

https://www.cnblogs.com/DafaRan/p/8192069.html

简介

Aspects是一个面向切面编程的库。
如果想深入了解iOS Runtime中的消息发送机制,Aspects的源码是值得分析的。 

项目主页
Aspects

整体分析

阅读Aspects的源码需要以下知识作为基础

  1. Objective-C Runtime
  2. 理解OC的消息分发机制
  3. KVO中的指针交换技术

阅读本文之前,建议应该先断点调试下Aspects的Demo,了解大致的过程。

核心实现

Aspects的核心实现就是利用Runtime中的消息分发机制如图:

Aspects通过把selector的方法替换为msg_forward方法转发 转而调用 forwardInvocation(forwardInvocation的实现被Aspects替换,将原来的方法实现与添加的实现组合在了一起)

核心源码分析

这是Aspects 面向切面编程的入口方法

- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add(self, selector, options, block, error); }复制代码

这段代码可以分三部分来看

  1. aspect_isSelectorAllowedAndTrack 这个方法 对父子类同时hook一个方法进行了一些限制
  2. aspect_getContainerForObject 通过Runtime添加关联值的方式 管理hook的方法
  3. aspect_prepareClassAndHookSelector 这是核心的实现,涉及到动态生成子类,改变isa指针的指向,改变方法的实现 一系列操作
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { NSCParameterAssert(self); NSCParameterAssert(selector); NSCParameterAssert(block); __block AspectIdentifier *identifier = nil; aspect_performLocked(^{ if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { //一个实例 只有一个container //这是区分实例对象和类对象的关键 //实例对象可以有很多个,但是同一个类的类对象只能有一个 AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); //原来的selector block identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { //container 里 存有 identifier (selector,block) [aspectContainer addAspect:identifier withOptions:options]; // Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error); } } }); return identifier; }复制代码

核心方法

aspect_prepareClassAndHookSelector这是核心的实现,涉及到动态生成子类,改变isa指针,改变方法的实现 一系列操作

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert(selector); //动态创建子类,改变forwardInvocation方法的实现 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]) { //子类的aliasSelector的实现为 当前类的selector __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); } //selector方法替换为_objc_msgForward // We use forwardInvocation to hook in. class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } }复制代码

动态生成子类,改变isa指针

#pragma mark - Hook Class static Class aspect_hookClass(NSObject *self, NSError **error) { NSCParameterAssert(self); //这里可以思考一下 class 方法 和 isa 的区别 //[self class] KVO可能改变了isa指针的指向 Class statedClass = self.class; // object_getClass 能准确的找到isa指针 Class baseClass = object_getClass(self); NSString *className = NSStringFromClass(baseClass); // Already subclassed //如果已经子类化了 就返回 if ([className hasSuffix:AspectsSubclassSuffix]) { return baseClass; //如果是类 就改掉类的forwardInvocation 而不是一个子类对象 // We swizzle a class object, not a single object. }else if (class_isMetaClass(baseClass)) { return aspect_swizzleClassInPlace((Class)self); //考虑到KVO,KVO的底层实现,交换了isa指针 // 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; } // forwardInvocation 替换成 (IMP)_ASPECTS_ARE_BEING_CALLED__ aspect_swizzleForwardInvocation(subclass); //子类的class方法返回当前被hook的对象的class aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass); objc_registerClassPair(subclass); } //将当前self设置为子类,这里其实只是更改了self的isa指针而已, 这里hook了子类的forwardInvocation方法,再次使用当前类时,其实是使用了子类的forwardInvocation方法。 object_setClass(self, subclass); return subclass; }复制代码

 


作者:Junyiii
链接:https://juejin.im/post/58f6d1675c497d006ca638fe
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
 

Aspects初始化工作核心部分的解析

aspect_add方法:

HookClass过程:

HookSelector过程

 

Aspects执行工作核心部分的解析

当我们正式向某个接受者发送消息的时候,会进行消息转发,而之前HookClass的过程当中我们已经对forwardInvocation的实现替换为了__ASPECTS_ARE_BEING_CALLED__

执行block的代码

流程图:

 

http://ios.jobbole.com/84522/


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM