如何实现Aspect,Aspects源码解析

作者:计算机网络

图片 1

用 OC 开垦 iOS 应用的人都晓得 OC 是一门动态语言。大家能够在运行时通过 Runtime API 获取并替换大肆方法。所以,在 AOP 和 热更新的实现上都日常可以看到 Runtime 的身影。那就算临近美好,但接收不当的话平时会产生重大 bug。Runtime 对于 OC 来讲就如吸重力强大但又不行朝不保夕的黑法力。

A delightful, simple library for aspect oriented programming

IOS中AOP框架Aspects源码深入分析

亲,小编的简书已不再维护和换代了,全部作品都迁移到了本人的村办博客:

AOP是Aspect Oriented Programming的缩写,意思正是面向切面编制程序。具体的演讲能够到维基百科上大概别的地点查看。在IOS中使用Swizzle技巧可以兑现面向切面编制程序,小编在RunTime应用实例--关于埋点的构思博文中也波及了Aspects框架,下边就来对该框架做以解析。

在RunTime应用实例--关于埋点的构思,也讲到了MethodSwizzle的本领,上边来看最平淡无奇的落到实处方案。譬如咱们要Hook住UIButton的sendAction:to:forEvent:,那个时候大家平时那样做:

 load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ SEL originSEL = @selector(sendAction:to:forEvent:); SEL swizzleSEL = @selector(swizzleSendAction:to:forEvent:); Class processedClass = [self class]; Method originMethod = class_getInstanceMethod(processedClass, originSEL); Method swizzleMethod = class_getInstanceMethod(processedClass, swizzleSEL); method_exchangeImplementations(originMethod, swizzleMethod); });}- swizzleSendAction:action to:target forEvent:(UIEvent *)event{// 执行相应的逻辑// 调用原来的系统方法[self swizzleSendAction:action to:target forEvent:event];}

实际的解说可以参见RunTime应用实例--关于埋点的钻探那篇作品。

  • 咱俩要给每个要Hook的艺术额外新加二个方法,方法的参数个数是形似的。
  • 譬如各类被Hook的办法内部的贯彻逻辑都平等,那么就供给在每一种新加上的主意中调用这段完结逻辑。
  • 咱俩新加的代码是在被Hook方法在此以前依然之后调用的呢?
  • 大家能放将以此新加的点子转变为三个Block呢?这样代码会更紧密,逻辑更清楚。
  • 万一这几个点子转化为Block,那么怎么样将这么些Block替换掉原本的方法达成Swizzle呢?如何在方便的时候调用那个block呢?

带着这几个主题素材大家来看Aspects是怎么兑现的。

  1. 动用和原方法一致参数分化格局名的方法,替换被hook的方法,这样系统在找不到这些办法的时候就能够走到forwardInvocation:
  2. 使用__ASPECTS_ARE_BEING_CALLED__更换掉系统的forwardInvocation:
  3. 给类扩张AspectsForwardInvocationSelectorName措施,它的贯彻是原来的forwardInvocation:的IMP。
  4. 当要hook的法子被调用时,系统会调用forwardInvocation:艺术。由于这些方法也被沟通掉了,所以会调用__ASPECTS_ARE_BEING_CALLED__
  5. __ASPECTS_ARE_BEING_CALLED__内部,先调用被hook方法以前的block,再调用替换被hook方法的block,以致从未替换的落实,最后调用被hook方法之之后的block。
  6. 假设hook出错,则再调用原本的AspectsForwardInvocationSelectorName的方法。
  • AspectInfo:存款和储蓄被hook方法的新闻。
  • AspectIdentifier:记录每三遍Aspect的消息。
  • AspectsContainer:有个别类依旧有个别对象具有被hook方法的聚集。
  • AspectTracker:对富有hook方法的操作。
  • NSInvocation :获取NSInvocation的参数。
  • NSObject :框架的要紧分类,定义公共接口。种种之间的关联如图:图片 2Aspect框架各个之间的构造

集体艺术重要调用:aspect_add情势,该措施内部首要调用几个艺术

  1. AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);赢得该办法对应的AspectsContainer

    • 就要hook的诀要转变为aliasSelector。
    • 赢得关联的AspectsContainer,若无,怎设置关联的AspectsContainer;
  2. identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];,将调用的参数封装成AspectIdentifier。

    • 通过:aspect_blockMethodSignature取的block对应的NSMethodSignature,
    • 创建AspectIdentifier再者重临
  3. aspect_prepareClassAndHookSelector(self, selector, error);,筹算专门的工作及hook方法。

    • aspect_hookClass,内部调用aspect_swizzleForwardInvocation,将系统的forwardInvocation: 替换为__ASPECTS_ARE_BEING_CALLED__
    • 在改方法内部分别调用被hook方法早前,替换被hook方法,被hook方法之后的秘籍。
 // Before hooks. aspect_invoke(classContainer.beforeAspects, info); aspect_invoke(objectContainer.beforeAspects, i nfo); // 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; } // After hooks. aspect_invoke(classContainer.afterAspects, info); aspect_invoke(objectContainer.afterAspects, info);

从当中能够见到Aspect首要应用了NSInvocation来防止了不一样参数个数的界定,通过将block调换为NSMethodSignature对象,可以达成对原来办法的交替可能Swizzle,然后调用其invoke方法,能够在适宜的空子触发这些block,通过标记将block分为,原方法早先,之后,替换原方法等做法。而不必像相近实现格局那样,改过各种新加措施中调用原本方式的岗位来落到实处。

  1. 万一有些类只只怕被此外几个类用到,那么能够将它们写到一个.m文本中,那是高内聚的一种展现。

  2. 类,包罗分类的面世,其实是为着越来越好的共青团和少先队代码,让代码的功效更清晰易懂。

  3. 将一段代码加锁的法子:能够将代码块作为参数,然后对其履行前后加锁,那样做的补益是:假如之后要换锁的品种,那么一旦在此个艺术中改培育足以了:例如:

    static void aspect_performLocked(dispatch_block_t block) {static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;OSSpinLockLock(&aspect_lock);block();OSSpinLockUnlock(&aspect_lock);}
    
  4. 能够通过自定义布局体的方法将block转变到NSMethodSignature对象,然后在适用的时候调用

    - invoke;- invokeWithTarget:target;
    

这三个方式来触发那么些block。具体做法请见:AspectBlockRef那几个布局体和aspect_blockMethodSignature那么些主意。此处有七个疑心:基于苹果官方提供的block定义,当中是从未signature这几个字段的:

 /* Revised new layout. */ struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (void *dst, void *src); void ; };

只是Aspect中的block转变为响应的构造体:AspectBlockRef layout = (__bridge void *)block;后来就能够活动有signature字段,这一点不是很明亮还望大神指教。

  1. 给定二个对象,和那个指标的SEL,假使参数比相当多,使用performSelector:withObject:这种形式不太方便,此时可以借鉴YYKit中的NSObject YYAdd里头的办法,假若此刻有NSInvocation对象,那么能够直接调用objc_msgSend(self, SEL, invocation)这种措施来落实。

在“Runtime保健室”住院的后两日,解析了一晃AOP的完结原理。“出院”后,开掘Aspect库还没有曾详细解析,于是就有了那篇小说,后天就来讲说iOS 是什么达成Aspect Oriented Programming。

图片 3

  • 关键字:面向切片编程OC动态性消息转发类型编码Swizzle...

  • 选取情状:

    • 1.集结处理逻辑
    • 2.在不改换源码的状态下,插入代码(如无侵染校勘第三方库代码,干一些坏坏的职业)
  • 1.Aspect Oriented Programming简介
  • 2.什么是Aspects
  • 3.Aspects 中4个基本类 深入深入分析
  • 4.Aspects hook前的备选干活
  • 5.Aspects hook进度精解
  • 6.关于Aspects的一些 “坑”

黑魔法.jpg

Aspects唯有一个类公事,相当轻量级,在促成的笔触上和JSPatch基本上。都首要运用OC的消息转发,最后都交给ForwardInvocation落到实处。二者超级多地点有异口同声之妙。

面向切面的主次设计(aspect-oriented programming,AOP,又译作面向方面的次第设计意见导向编制程序断面导向程序设计)是Computer科学中的二个术语,指一种程序设计范型。该范型以一种名叫侧面(aspect,又译作方面)的语言布局为根底,侧面是一种新的模块化学工业机械制,用来陈说分散在目的、类或函数中的横断关怀点(crosscutting concern)。

1 新闻转发

iOS 的法子施行是由此 SEL 找到 对应的 IMP,然后施行相应的函数。如若 SEL 找不到相应的 IMP 就能够运维新闻转载机制。音讯转发会经过八个阶段。

  • 先是阶段:在类的此中进行拍卖

      //处理实例方法
        (BOOL)resolveInstanceMethod:(SEL)sel 
      //处理类方法
        (BOOL)resolveClassMethod:(SEL)sel
    

    重写该形式可以对相应的 SEL 进行拍卖,重返 YES 表达拍卖到位,不开展下一步转载。

  • 其次等第:搜索备援选取者

      - (id)forwardingTargetForSelector:(SEL)aSelector
    

    该方法会重临三个能够管理改新闻的备援接纳者。若是回去 nil 则持续下一步。

  • 其三阶段:全局管理

      - (void)forwardInvocation:(NSInvocation *)invocation
    

    将音信包装成三个 NSInvocation 对象,让系统对其开展拍卖。就算这里依旧尚未张开,则会抛出相应的老大。

万事工艺流程如下图所示:

图片 4

音讯转发.jpg

咱俩掌握 OC 是动态语言,大家实践叁个函数的时候,其实是在发一条音信:[receiver message],这几个进度正是依据 message 生成 selector,然后依照 selector 寻觅指向函数具体贯彻的指针IMP,然后找到真正的函数实践逻辑。这种管理流程给大家提供了动态性的恐怕,试想一下,要是在运行时,动态的改动了 selector 和 IMP 的相应关系,那么就会使得本来的[receiver message]跻身到新的函数实现了。

右边包车型地铁概念来源于对面向对象的前后相继设计的改正,但并不只限于此,它还是能用来修改守旧的函数。与侧边相关的编制程序概念还包罗元对象公约、核心、混入和嘱托。

2 Aspects

Aspects 通过 swizzling method 和 swizzling isa 完成对原方法的 hook ,以此来赞助使用者达成 AOP 编制程序。

抑或先来推广一下:

AOP通过预编写翻译情势和平运动行期动态代理落到实处程序成效的统一爱抚的一种技巧。

2.1 Aspect 重要首要类介绍:

@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end

AspectInfo:aspect 的音信,被 hook 方法的 IMP 消息贮存在 NSInvocation 中。

@interface AspectIdentifier : NSObject
  (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end

AspectIdentifier:一个 aspect 的具体音信,相应的 sel,options,block

@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end

AspectsContainer:本类全数的 aspect 的容器

@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end

AspectTracker:用来追踪 aspect 的有关新闻

OC上,每个类都是那般二个布局体:

OOP针对职业管理进度的实体及其属性行为进行虚幻封装,以获得越来越明显高效的逻辑单元区划。

2.2 Aspects API调用之后发出的事体

上面是 aspect 相关措施调用之后,首要的实行逻辑

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)) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

aspect 方法入口,先是断言剖断传入的参数。然后 aspect_performLocked 加锁同步操作,通过 aspect_isSelectorAllowedAndTrack 推断该办法是还是不是同意 hook。推断逻辑如下:

  1. 实例方法的"retain", "release", "autorelease", "forwardInvocation:" 不容许 hook , "dealloc" 方法只好是 AspectPositionBefore 的艺术操作
  2. 类措施要一口咬住不放父类,子类中并未 hook 过该办法。

假设得以 hook,则基于参数生成二个 AspectIdentifier 对象,之后放在相应的 AspectsContainer 容器中,然后调用 aspect_prepareClassAndHookSelector 进行 class swizzling 和 method swizzling。

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_hookClass 举行相应的 class swizzling 操作。

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;
}

class swizzling 的入眼逻辑如下:

  1. 若 class 是元类,swizzling 相应的 ForwardInvocation 方法
  2. 若 class 是平日的类, 先判断是不是是 KVO 对象,是的话 swizzling 相应的 ForwardInvocation 方法
  3. 若不是 KVO 对象的话,先创制一个子类,并 swizzling 子类的 ForwardInvocation,同期将本类的 isa 指针,指向创制的子类。

hook 过的 class 都封存在 swizzledClasses 对象中。接下去进行 method swizzling, 调用 aspect_isMsgForwardIMP 确认达成函数不是音讯转载相关的。然后让 aliasSelector 指向 targetMethodIMP,本来的 OriginalSel 指向 msgForwardIMP。至此设置阶段就做到了。

struct objc_class { struct objc_class * isa; const char *name; …. struct objc_method_list **methodLists; /*方法链表*/};

AOP则是照准职业处理进度中的切面张开领取,它所面前遇到的是管理进程中的有个别步骤阶段,以获取逻辑进度中各部分之间低耦合性的隔开分离效果

2.3 被 Hook 的措施被调用之后发生的事体

在调用 aspect 的 api 进行连锁安装之后,程序推行到刚被 hook 过的办法就能够进来 aspect 的实践逻辑。

出于地点的设置,我们清楚调用实例方法的类的 isa 会指向了叁个子类,然后子类的 OriginalSel 指向了 msgForwardIMP, 而子类相应的 ForwardInvocation 也被调换了,指向了 __ASPECTS_ARE_BEING_CALLED__,所以,一旦调用相应的主意就能够进去 __ASPECTS_ARE_BEING_CALLED__ 里面。

如假诺类方式,则是 OriginalSel 指向了 msgForwardIMP ,自己的 ForwardInvocation 被交替指向了__ASPECTS_ARE_BEING_CALLED__ ,所以相仿踏向到 __ASPECTS_ARE_BEING_CALLED__ 方法里。

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)];
}

__ASPECTS_ARE_BEING_CALLED__ 内部的逻辑是:依照 aliasSelector 获取在此之前存款和储蓄的类照旧元类的 AspectsContainer 对象,之后依据 AspectOptions 在方便的机缘获取 AspectsContainer 中对应的保有 AspectIdentifier 并施行相应的 block 。
而且管理被 hook 方法原本的兑现( IMP State of Qatar , 被封装在 originalInvocation 中(若有 AspectIdentifier 的 AspectOptions 是 insteadAspects ,则 originalInvocation 将不会被实践)。若 AspectIdentifier 的 AspectOptions 是 AspectOptionAutomaticRemoval ,则该 AspectIdentifier 会被存入 aspectsToRemove 中,实行落成以往会被移除。

其中 methodList 方法链表里存款和储蓄的是 Method类型:

OOP和AOP归于八个不等的“构思格局”。OOP静心于对象的性质和行为的包裹,AOP专心于管理某些步骤和级差的,从当中进行切面的领到。

3 总结

以上正是本身对此 Aspects 那一个强盛的 AOP 库的四个概略了然。同一时候这其间还会有相当多细节很值得学习。

typedef struct objc_method *Method;typedef struct objc_ method { SEL method_name; char *method_types; IMP method_imp;};

比如,假如有四个断定权限的必要,OOP的做法必定将是在各种操作前都步入权限决断。那日志记录如何做?在各种方法的发轫终结之处都加上日志记录。AOP正是把这么些再一次的逻辑和操作,提抽取来,运用动态代理,完成这么些模块的解耦。OOP和AOP不是排斥,而是相互合作。

4 参照他事他说加以考察小说

面向切面编制程序之 Aspects 源码剖析及利用

Method 保存了二个格局的满贯消息,蕴含 SEL 方法名,type各参数和再次回到值类型,IMP该办法具体落实的函数指针。

在iOS里面使用AOP实行编制程序,可以达成非侵入。没有必要转移在此之前的代码逻辑,就能够投入新的效劳。主要用来管理局地怀有横断性质的系统性服务,如日志记录、权限管理、缓存、对象池管理等。

通过 Selector 调用艺术时,会从methodList 链表里找到呼应Method拓宽调用,这一个 methodList上的的成分是足以动态替换的,可以把某部Selector相应的函数指针IMP替换到新的,也能够得到已部分某些 Selector 对应的函数指针IMP,让另三个Selector 跟它对应,Runtime提供了一部分接口做那几个事。

图片 5

比如:

Aspects是一个轻量级的面向切面编制程序的库。它能容许你在每一个类和每三个实例中存在的艺术里面参加别的代码。能够在以下切入点插入代码:before(在原始的格局前试行State of Qatar/ instead(替换原有的法子执行State of Qatar / after(在原本的办法后试行,默许卡塔尔(قطر‎。通过Runtime音讯转载完成Hook。Aspects会自动的调用super方法,使用method swizzling起来会越加实惠。

static void viewDidLoadIMP (id slf, SEL sel) { // Custom Code}Class cls = NSClassFromString(@"UIViewController");SEL selector = @selector(viewDidLoad);Method method = class_getInstanceMethod(cls, selector);//获得viewDidLoad方法的函数指针IMP imp = method_getImplementation//获得viewDidLoad方法的参数类型char *typeDescription = method_getTypeEncoding;//新增一个ORIGViewDidLoad方法,指向原来的viewDidLoad实现class_addMethod(cls, @selector(ORIGViewDidLoad), imp, typeDescription);//把viewDidLoad IMP指向自定义新的实现class_replaceMethod(cls, selector, viewDidLoadIMP, typeDescription);

这些库很平静,近期用在数百款app上了。它也是PSPDFKit的一有的,PSPDFKit是三个iOS 看PDF的framework库。作者最后决定把它开源出来。

那般就把 UIViewController-viewDidLoad办法给替换到我们自定义的不二诀窍,APP里调用 UIViewControllerviewDidLoad 方法都会去到上述 viewDidLoadIMP 函数里,在此个新的IMP函数里调用新扩充的措施,就贯彻了改造viewDidLoad 方法,同时为 UIViewController新扩展了个办法 -ORIGViewDidLoad针对原本viewDidLoadIMP, 能够由此那几个方式调用到原本的完结。

咱俩初阶文件开首看起。

.Aspect要的是促成二个通用的IMP,任性方法任性参数都得以通过这一个IMP中间转播。上边讲的都以照准某多个措施的替换,但即便这些艺术有参数,如何把参数值传给大家新的 IMP 函数呢?例如 UIViewController-viewDidAppear:主意,调用者会传八个 Bool值,大家须求在和煦完结的IMP(上述的viewDidLoadIMP)上取得这么些值,如何能获得?假设只是指向八个艺术写IMP,是能够直接获得那几个参数值的。如何达到通用的功力啊?

1.Aspects.h
typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, /// Called after the original implementation  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.};

在头文件中定义了贰个枚举。这一个枚举里面是调用切成条方法的机遇。默许是AspectPositionAfter在原方法施行完事后调用。AspectPositionInstead是替换原方法。AspectPositionBefore是在原方法从前调用切条方法。AspectOptionAutomaticRemoval是在hook推行完自动移除。

@protocol AspectToken <NSObject>- remove;@end

概念了一个AspectToken的磋商,这里的Aspect Token是隐式的,允许大家调用remove去撤消一个hook。remove方法再次回到YES代表废除成功,重返NO就撤消失利。

@protocol AspectInfo <NSObject>- instance;- (NSInvocation *)originalInvocation;- (NSArray *)arguments;@end

又定义了二个AspectInfo左券。AspectInfo protocol是我们block语法里面包车型客车首先个参数。

instance方法再次来到当前被hook的实例。originalInvocation方法再次回到被hooked方法的原始的invocation。arguments方法重回全数办法的参数。它的落实是懒加载。

头文件中还专程给了一段注释来声明Aspects的用法和注意点,值得大家关怀。

/** Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second. Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe. */

Aspects利用的OC的讯息转运载飞机制,hook新闻。那样会有一部分属性费用。不要把Aspects加到平时被接纳的主意里面。Aspects是用来布置给view/controller 代码使用的,并不是用来hook每秒调用1000次的章程的。

增多Aspects之后,会回到叁个隐式的token,这么些token会被用来注销hook方法的。全部的调用都以线程安全的。

有关线程安全,下边会详细深入分析。现在起码我们理解Aspects不应当被用在for循环这一个办法里面,会引致不小的习性损耗。

@interface NSObject /// Adds a block of code before/instead/after the current `selector` for a specific class.////// @param block Aspects replicates the type signature of the method being hooked./// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method./// These parameters are optional and will be filled to match the block signature./// You can even use an empty block, or one that simple gets `id<AspectInfo>`.////// @note Hooking static methods is not supported./// @return A token which allows to later deregister the aspect.  (id<AspectToken>)aspect_hookSelector:selector withOptions:(AspectOptions)options usingBlock:block error:(NSError **)error;/// Adds a block of code before/instead/after the current `selector` for a specific instance.- (id<AspectToken>)aspect_hookSelector:selector withOptions:(AspectOptions)options usingBlock:block error:(NSError **)error;@end

Aspects整个Curry面就唯有那七个方法。这里能够看出,Aspects是NSobject的二个extension,只借使NSObject,都足以动用那八个办法。那三个点子名字都是同一个,入参和重回值也千人一面,独一不相同的是八个是加号方法一个是减号方法。四个是用来hook类措施,五个是用来hook实例方法。

办法里面有4个入参。第叁个selector是要给它扩充切面的原方法。第三个参数是AspectOptions类型,是意味着这几个切成条扩大在原方法的before / instead / after。第一个参数是回来的大错特错。

珍视的正是第四个入参block。那些block复制了正在被hook的艺术的签定signature类型。block信守AspectInfo公约。大家居然能够动用叁个空的block。AspectInfo契约里面包车型地铁参数是可选的,主若是用来相配block签字的。

重回值是三个token,能够被用来注销这么些Aspects。

只顾,Aspects是不匡助hook 静态static方法的

typedef NS_ENUM(NSUInteger, AspectErrorCode) { AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted. AspectErrorDoesNotRespondToSelector, /// Selector could not be found. AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed. AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed. AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair. AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called. AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large. AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.};extern NSString *const AspectErrorDomain;

此地定义了错误码的类型。出错的时候低价大家调节和测量试验。

什么样兑现方式替换

  • va_list完毕(三回抽取方法的参数卡塔尔

这段代码摘至JSPatch:

static void commonIMP(id slf, ...) va_list args; va_start(args, slf); NSMutableArray *list = [[NSMutableArray alloc] init]; NSMethodSignature *methodSignature = [cls instanceMethodSignatureForSelector:selector]; NSUInteger numberOfArguments = methodSignature.numberOfArguments; id obj; for (NSUInteger i = 2; i < numberOfArguments; i  ) { const char *argumentType = [methodSignature getArgumentTypeAtIndex:i]; switch(argumentType[0]) { case 'i': obj = @(va_arg(args, int)); break; case 'B': obj = @(va_arg(args, BOOL)); break; case 'f': case 'd': obj = @(va_arg(args, double)); break; …… //其他数值类型 default: { obj = va_arg; break; } } [list addObject:obj]; } va_end; [function callWithArguments:list];}

如此那般不管情势参数是哪些,有稍许个,都足以经过va_list的一组方法一个个抽取来,组成 NSArray 。很圆随处减轻了参数的主题素材,一贯运行如常,然则在arm64va_list 的结构改换了,引致爱莫能助上述如此取参数。

之所以供给找到另一种办法。

  • ForwardInvocation实现

    • 看图说话

图片 6

从地方大家可以发现,在发音讯的时候,假设 selector 有对应的 IMP ,则直接实施,若无,oc给大家提供了多少个可供补救的机缘,依次有 resolveInstanceMethodforwardingTargetForSelectorforwardInvocation

Aspects故而选拔在 forwardInvocation 这里管理是因为,那多少个等第特点都不太一致:

  • resolvedInstanceMethod: 相符给类/对象动态增进八个志趣相同的贯彻,
  • forwardingTargetForSelector:符合将新闻转载给其余对象处理,
  • forwardInvocation: 是中间最灵敏,最能相符必要的。

因此 Aspects的方案正是,对于待 hookselector,将其指向 objc_msgForward / _objc_msgForward_stret ,同期生成三个新的 aliasSelector 指向原本的 IMP,而且 hookforwardInvocation函数,通过forwardInvocation调用到原本的IMP。

中心原理:遵照上面包车型地铁思绪,当被 hookselector 被实践的时候,首先根据 selector找到了 objc_msgForward / _objc_msgForward_stret ,而这一个会触发音讯转载,从而步入 forwardInvocation。同期由于forwardInvocation 的针对性也被涂改了,由此会转入新的 forwardInvocation函数,在里头实施须要停放的附加代码,完毕现在,再折路再次回到原本的 IMP

差不离流程如下:

图片 7摘至

-forwardInvocation:情势的落实给替换掉了,假设程序里真有用到这么些措施对消息实行转载,原来的逻辑如何做?首先大家在交替 -forwardInvocation:方法前会新建一个方法 -ORIGforwardInvocation:,保存原本的兑现IMP,在新的 -forwardInvocation:实现里做了个判断,借使转会的格局是大家想改写的,就走大家的逻辑,若不是,就调 -ORIGforwardInvocation:走原本的流程。

将了这么多或许有个别饶。Talk is sheap,show me the code

开始文件中得以见到接受aspects有二种选择形式:

  • 1.类方法
  • 2.实例方法
/// Adds a block of code before/instead/after the current `selector` for a specific class.////// @param block Aspects replicates the type signature of the method being hooked./// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method./// These parameters are optional and will be filled to match the block signature./// You can even use an empty block, or one that simple gets `id<AspectInfo>`.////// @note Hooking static methods is not supported./// @return A token which allows to later deregister the aspect.  (id<AspectToken>)aspect_hookSelector:selector withOptions:(AspectOptions)options usingBlock:block error:(NSError **)error;/// Adds a block of code before/instead/after the current `selector` for a specific instance.- (id<AspectToken>)aspect_hookSelector:selector withOptions:(AspectOptions)options usingBlock:block error:(NSError **)error;

两侧的严重性原理基本大概.

先来探视有怎么着定义:

2.Aspects.m
#import "Aspects.h"#import <libkern/OSAtomic.h>#import <objc/runtime.h>#import <objc/message.h>

#import <libkern/OSAtomic.h>导入那些头文件是为着上边用到的自旋锁。#import <objc/runtime.h> 和 #import <objc/message.h>是行使Runtime的供给头文件。

typedef NS_OPTIONS(int, AspectBlockFlags) { AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25), AspectBlockFlagsHasSignature = (1 << 30)};

概念了AspectBlockFlags,那是七个flag,用来标识两种状态,是还是不是必要Copy和Dispose的Helpers,是或不是须要艺术具名Signature 。

在Aspects中定义的4个类,分别是AspectInfo,AspectIdentifier,AspectsContainer,AspectTracker。接下来就分别拜谒那4个类是怎么定义的。

AspectOptions

typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, /// Called after the original implementation  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.};

概念切成条的调用机遇

3. AspectInfo
@interface AspectInfo : NSObject <AspectInfo>- initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;@property (nonatomic, unsafe_unretained, readonly) id instance;@property (nonatomic, strong, readonly) NSArray *arguments;@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;@end

AspectInfo对应的实现

#pragma mark - AspectInfo@implementation AspectInfo@synthesize arguments = _arguments;- initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation { NSCParameterAssert; NSCParameterAssert(invocation); if (self = [super init]) { _instance = instance; _originalInvocation = invocation; } return self;}- (NSArray *)arguments { // Lazily evaluate arguments, boxing is expensive. if (!_arguments) { _arguments = self.originalInvocation.aspects_arguments; } return _arguments;}

AspectInfo是持续于NSObject,而且遵照了AspectInfo公约。在其 - initWithInstance: invocation:方法中,把外围传进来的实例instance,和原有的invocation保存到AspectInfo类对应的积极分子变量中。- (NSArray *卡塔尔arguments方法是八个懒加载,再次来到的是原来的invocation里面包车型客车aspects参数数组。

aspects_arguments那个getter方法是怎么贯彻的吧?小编是透过四个为NSInvocation加多四个分拣来兑现的。

@interface NSInvocation - (NSArray *)aspects_arguments;@end

为原本的NSInvocation类增多二个Aspects分类,这几个分类中只扩大二个方法,aspects_arguments,再次来到值是三个数组,数组里面含有了脚下invocation的富有参数。

对应的完结

#pragma mark - NSInvocation @implementation NSInvocation - (NSArray *)aspects_arguments { NSMutableArray *argumentsArray = [NSMutableArray array]; for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx  ) { [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null]; } return [argumentsArray copy];}@end

- (NSArray *)aspects_arguments完毕相当粗略,正是一层for循环,把methodSignature方法签字里面包车型大巴参数,都参与到数组里,最后把数组再次回到。

至于获取格局全部参数的这么些- (NSArray *)aspects_arguments方法的兑现,有2个地点须求详细表明。一是怎么循环从2起来,二是[self aspect_argumentAtIndex:idx]里面是怎么贯彻的。

图片 8

先来讲说怎么循环从2开端。

Type Encodings作为对Runtime的增补,编写翻译器将每一个方法的再次回到值和参数类型编码为三个字符串,并将其与方法的selector关联在一块儿。这种编码方案在此外意况下也是老大实用的,由此大家得以利用@encode编写翻译器指令来收获它。当给定叁个类型时,@encode再次来到那些项目标字符串编码。那几个品种能够是诸如int、指针那样的宗旨项目,也得以是布局体、类等品种。事实上,任何可以视作sizeof(卡塔尔国操作参数的品种都能够用于@encode(State of Qatar。

在Objective-C Runtime Programming Guide中的Type Encoding一节中,列出了Objective-C中具有的品种编码。须要留意的是这一个项目非常多是与大家用来存档和分发的编码类型是相同的。但有一点点不可能在存档时选拔。

注:Objective-C不帮衬long double类型。@encode(long double卡塔尔(قطر‎再次来到d,与double是同等的。

图片 9

OC为支撑音讯的中转和动态调用,Objective-C Method 的 Type 音讯以 “再次回到值 Type 参数 Types” 的款式结缘编码,还亟需思虑到 self和 _cmd 那八个包蕴参数:

- tap; => "v@:"- tapWithView:pointx; => "i@:d"

规行矩步地点的表,咱们能够知晓,编码出来的字符串,前3位分别是回来值Type,self隐含参数Type @,_cmd隐含参数Type :。

据此从第4位伊始,是入参。

假诺大家以- tapView:view atIndex:(NSInteger卡塔尔index为例,打字与印刷一下methodSignature

 po self.methodSignature<NSMethodSignature: 0x60800007df00>number of arguments = 4frame size = 224is special struct return? NOreturn value: -------- -------- -------- --------type encoding  'v'flags {}modifiers {}frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}memory {offset = 0, size = 0}argument 0: -------- -------- -------- --------type encoding  '@'flags {isObject}modifiers {}frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}argument 1: -------- -------- -------- --------type encoding  ':'flags {}modifiers {}frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}argument 2: -------- -------- -------- --------type encoding  '@'flags {isObject}modifiers {}frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}argument 3: -------- -------- -------- --------type encoding  'q'flags {isSigned}modifiers {}frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}

number of arguments = 4,因为有2个包涵参数self和_cmd,加上入参view和index。

argument return value 0 1 2 3
methodSignature v @ : @ q

首先个argument的frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}memory {offset = 0, size = 0},再次来到值在此边不占size。第二个argument是self,frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}。由于size = 8,下三个frame的offset正是8,之后是16,由此及彼。

有关为什么这里要传递2,还跟aspect_argumentAtIndex具体落到实处有涉嫌。

再来看看aspect_argumentAtIndex的具体得以完结。那个方法还要多谢ReactiveCocoa团队,为获取情势签名的参数提供了一种高雅的落到实处格局。

// Thanks to the ReactiveCocoa team for providing a generic solution for this.- aspect_argumentAtIndex:(NSUInteger)index { const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; // Skip const type qualifier. if (argType[0] == _C_CONST) argType  ;#define WRAP_AND_RETURN do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @; } while  if (strcmp(argType, @encode == 0 || strcmp(argType, @encode == 0) { __autoreleasing id returnObj; [self getArgument:&returnObj atIndex:(NSInteger)index]; return returnObj; } else if (strcmp(argType, @encode == 0) { SEL selector = 0; [self getArgument:&selector atIndex:(NSInteger)index]; return NSStringFromSelector;} else if (strcmp(argType, @encode == 0) { __autoreleasing Class theClass = Nil; [self getArgument:&theClass atIndex:(NSInteger)index]; return theClass; // Using this list will box the number with the appropriate constructor, instead of the generic NSValue. } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode(long long)) == 0) { WRAP_AND_RETURN(long long); } else if (strcmp(argType, @encode(unsigned char)) == 0) { WRAP_AND_RETURN(unsigned char); } else if (strcmp(argType, @encode(unsigned int)) == 0) { WRAP_AND_RETURN(unsigned int); } else if (strcmp(argType, @encode(unsigned short)) == 0) { WRAP_AND_RETURN(unsigned short); } else if (strcmp(argType, @encode(unsigned long)) == 0) { WRAP_AND_RETURN(unsigned long); } else if (strcmp(argType, @encode(unsigned long long)) == 0) { WRAP_AND_RETURN(unsigned long long); } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN; } else if (strcmp(argType, @encode == 0) { WRAP_AND_RETURN(const char *); } else if (strcmp(argType, @encode == 0) { __unsafe_unretained id block = nil; [self getArgument:&block atIndex:(NSInteger)index]; return [block copy]; } else { NSUInteger valueSize = 0; NSGetSizeAndAlignment(argType, &valueSize, NULL); unsigned char valueBytes[valueSize]; [self getArgument:valueBytes atIndex:(NSInteger)index]; return [NSValue valueWithBytes:valueBytes objCType:argType]; } return nil;#undef WRAP_AND_RETURN}

getArgumentTypeAtIndex:那一个情势是用来获得到methodSignature方法具名内定index的type encoding的字符串。这一个法子传出去的字符串直接正是大家传进去的index值。比方大家传进去的是2,其实传出来的字符串是methodSignature对应的字符串的第二位。

出于第0位是函数再次来到值return value对应的type encoding,所以传进来的2,对应的是argument2。所以大家那边传递index = 2进来,便是过滤掉了前3个type encoding的字符串,从argument2早先相比。这正是干吗循环从2起先的缘故。

图片 10

_C_CONST是贰个常量,用来判定encoding的字符串是或不是CONST常量。

#define _C_ID '@'#define _C_CLASS '#'#define _C_SEL ':'#define _C_CHR 'c'#define _C_UCHR 'C'#define _C_SHT 's'#define _C_USHT 'S'#define _C_INT 'i'#define _C_UINT 'I'#define _C_LNG 'l'#define _C_ULNG 'L'#define _C_LNG_LNG 'q'#define _C_ULNG_LNG 'Q'#define _C_FLT 'f'#define _C_DBL 'd'#define _C_BFLD 'b'#define _C_BOOL 'B'#define _C_VOID 'v'#define _C_UNDEF '?'#define _C_PTR '^'#define _C_CHARPTR '*'#define _C_ATOM '%'#define _C_ARY_B '['#define _C_ARY_E ']'#define _C_UNION_B '('#define _C_UNION_E ')'#define _C_STRUCT_B '{'#define _C_STRUCT_E '}'#define _C_VECTOR '!'#define _C_CONST 'r'

那边的Type和OC的Type 是截然一致的,只可是这里是一个C的char类型。

#define WRAP_AND_RETURN do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @; } while 

WRAP_AND_RETUHavalN是八个宏定义。这一个宏定义里面调用的getArgument:atIndex:方法是用来在NSInvocation中依据index得到相应的Argument,最终return的时候把val包装成靶子,再次回到出去。

在底下大段的if - else决断中,有相当多字符串比较的函数strcmp。

举个例子说strcmp(argType, @encode == 0,argType是一个char,内容是methodSignature抽取来对应的type encoding,和@encode是同样的type encoding。通过strcmp相比过后,固然是0,代表类型是肖似的。

上边包车型地铁大段的论断正是把入参都重返的进程,依次决断了id,class,SEL,接着是一大推基本项目,char,int,short,long,long long,unsigned char,unsigned int,unsigned short,unsigned long,unsigned long long,float,double,BOOL,bool,char *那一个基本项目都会动用WRAP_AND_RETUQashqaiN打包成靶子回来。最终判别block和struct布局体,也会回到对应的对象。

这样入参就都回去到数组里面被收取了。若是依旧地点- tapView:view atIndex:(NSInteger卡塔尔国index为例子,推行完aspects_arguments,数组里面装的的是:

( <UIView: 0x7fa2e2504190; frame = (0 80; 414 40); layer = <CALayer: 0x6080000347c0>>", 1)

计算,AspectInfo里面主即便 NSInvocation 消息。将NSInvocation包装一层,比方参数音讯等。

AspectErrorCode

typedef NS_ENUM(NSUInteger, AspectErrorCode) { AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted. AspectErrorDoesNotRespondToSelector, /// Selector could not be found. AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed. AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed. AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair. AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called. AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large. AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.};

此地定义了在施行的时候的错误码,在常常开垦中大家也时常应用这种方法,特别是在概念互联网乞请的时候。

4. AspectIdentifier

图片 11

// Tracks a single aspect.@interface AspectIdentifier : NSObject  (instancetype)identifierWithSelector:selector object:object options:(AspectOptions)options block:block error:(NSError **)error;- invokeWithInfo:(id<AspectInfo>)info;@property (nonatomic, assign) SEL selector;@property (nonatomic, strong) id block;@property (nonatomic, strong) NSMethodSignature *blockSignature;@property (nonatomic, weak) id object;@property (nonatomic, assign) AspectOptions options;@end

对应得以达成

#pragma mark - AspectIdentifier@implementation AspectIdentifier  (instancetype)identifierWithSelector:selector object:object options:(AspectOptions)options block:block error:(NSError **)error { NSCParameterAssert; NSCParameterAssert; NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc. if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) { return nil; } AspectIdentifier *identifier = nil; if (blockSignature) { identifier = [AspectIdentifier new]; identifier.selector = selector; identifier.block = block; identifier.blockSignature = blockSignature; identifier.options = options; identifier.object = object; // weak } return identifier;}- invokeWithInfo:(id<AspectInfo>)info { NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature]; NSInvocation *originalInvocation = info.originalInvocation; NSUInteger numberOfArguments = self.blockSignature.numberOfArguments; // Be extra paranoid. We already check that on hook registration. if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { AspectLogError(@"Block has too many arguments. Not calling %@", info); return NO; } // The `self` of the block will be the AspectInfo. Optional. if (numberOfArguments > 1) { [blockInvocation setArgument:&info atIndex:1]; } void *argBuf = NULL; for (NSUInteger idx = 2; idx < numberOfArguments; idx  ) { const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); if (!(argBuf = reallocf(argBuf, argSize))) { AspectLogError(@"Failed to allocate memory for block invocation."); return NO; } [originalInvocation getArgument:argBuf atIndex:idx]; [blockInvocation setArgument:argBuf atIndex:idx]; } [blockInvocation invokeWithTarget:self.block]; if (argBuf != NULL) { free; } return YES;}- (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];}- remove { return aspect_remove(self, NULL);}@end

在instancetype方法中调用了aspect_blockMethodSignature方法。

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) { AspectBlockRef layout = (__bridge void *)block; if (!(layout->flags & AspectBlockFlagsHasSignature)) { NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block]; AspectError(AspectErrorMissingBlockSignature, description); return nil; } void *desc = layout->descriptor; desc  = 2 * sizeof(unsigned long int); if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) { desc  = 2 * sizeof; } if  { NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block]; AspectError(AspectErrorMissingBlockSignature, description); return nil; } const char *signature = (*(const char **)desc); return [NSMethodSignature signatureWithObjCTypes:signature];}

这个aspect_blockMethodSignature的指标是把传递步向的AspectBlock调换来NSMethodSignature的法门签字。

AspectBlock的布局如下

typedef struct _AspectBlock { __unused Class isa; AspectBlockFlags flags; __unused int reserved; void (__unused *invoke)(struct _AspectBlock *block, ...); struct { unsigned long int reserved; unsigned long int size; // requires AspectBlockFlagsHasCopyDisposeHelpers void (void *dst, const void *src); void (const void *); // requires AspectBlockFlagsHasSignature const char *signature; const char *layout; } *descriptor; // imported variables} *AspectBlockRef;

此地定义了一个Aspects内部使用的block类型。对系统的Block很熟识的同学一眼就能以为互相很像。面生的能够看看自身前边解析Block的篇章。小说里,用Clang把Block转变到构造体,结商谈这里定义的block很日常。

图片 12

摸底了AspectBlock的组织从今以后,再看aspect_blockMethodSignature函数就相比较清楚了。

 AspectBlockRef layout = (__bridge void *)block; if (!(layout->flags & AspectBlockFlagsHasSignature)) { NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block]; AspectError(AspectErrorMissingBlockSignature, description); return nil; }

AspectBlockRef layout = (__bridge void *卡塔尔(قطر‎block,由于双方block达成相符,所以那边先把入参block压迫转变来AspectBlockRef类型,然后判别是还是不是有AspectBlockFlagsHasSignature的标记位,若无,报不包罗方法签字的error。

精心,传入的block是大局类型的

 (__NSGlobalBlock) __NSGlobalBlock = { NSBlock = { NSObject = { isa = __NSGlobalBlock__ } } }

 void *desc = layout->descriptor; desc  = 2 * sizeof(unsigned long int); if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) { desc  = 2 * sizeof; } if  { NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block]; AspectError(AspectErrorMissingBlockSignature, description); return nil; }

desc就是原本block里面临应的descriptor指针。descriptor指针往下偏移2个unsigned long int的职分就照准了copy函数的地址,要是带有Copy和Dispose函数,那么继续往下偏移2个的大小。当时指针料定移动到了const char *signature的岗位。假诺desc不设有,那么也会报错,该block不包括方法签字。

 const char *signature = (*(const char **)desc); return [NSMethodSignature signatureWithObjCTypes:signature];

到了此地,就确定保障有法子签字,且存在。最终调用NSMethodSignature的signatureWithObjCTypes方法,再次来到方法签字。

比方表明aspect_blockMethodSignature最后生成的不二诀要签字是怎样子的。

 [UIView aspect_hookSelector:@selector(UIView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspects, UIView *view, NSInteger index) { NSLog(@"按钮点击了 %ld",index); } error:nil];

const char *signature最终赢得的字符串是那般

(const char *) signature = 0x0000000102f72676 "v32@?0@"<AspectInfo>"8@"UIView"16q24"

v32@?0@"<AspectInfo>"8@"UIView"16q24是Block

^(id<AspectInfo> aspects, UIView *view, NSInteger index){}

对应的Type。void再次来到值的Type是v,32是offset,@?是block对应的Type,@“<AspectInfo>”是第叁个参数,@"UIView"是第二个参数,NSInteger对应的Type就是q了。

各个Type后边跟的数字都是它们分别对应的offset。把最后转变好的NSMethodSignature打印出来。

 <NSMethodSignature: 0x600000263dc0> number of arguments = 4 frame size = 224 is special struct return? NO return value: -------- -------- -------- -------- type encoding  'v' flags {} modifiers {} frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0} memory {offset = 0, size = 0} argument 0: -------- -------- -------- -------- type encoding  '@?' flags {isObject, isBlock} modifiers {} frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} argument 1: -------- -------- -------- -------- type encoding  '@"<AspectInfo>"' flags {isObject} modifiers {} frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} conforms to protocol 'AspectInfo' argument 2: -------- -------- -------- -------- type encoding  '@"UIView"' flags {isObject} modifiers {} frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8} class 'DLMenuView' argument 3: -------- -------- -------- -------- type encoding  'q' flags {isSigned} modifiers {} frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8}

归来AspectIdentifier中继续看instancetype方法,获取到了传播的block的方法具名之后,又调用了aspect_isCompatibleBlockSignature方法。

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) { NSCParameterAssert(blockSignature); NSCParameterAssert; NSCParameterAssert; BOOL signaturesMatch = YES; NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector]; if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) { signaturesMatch = NO; }else { if (blockSignature.numberOfArguments > 1) { const char *blockType = [blockSignature getArgumentTypeAtIndex:1]; if (blockType[0] != '@') { signaturesMatch = NO; } } // Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2. // The block can have less arguments than the method, that's ok. if (signaturesMatch) { for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx  ) { const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; // Only compare parameter, not the optional type data. if (!methodType || !blockType || methodType[0] != blockType[0]) { signaturesMatch = NO; break; } } } } if (!signaturesMatch) { NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature]; AspectError(AspectErrorIncompatibleBlockSignature, description); return NO; } return YES;}

本条函数的效益是把我们要替换的方法block和要替换的原方法,举行相比。如何对待呢?比较两个的艺术签字。

入参selector是原方法。

if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) { signaturesMatch = NO; }else { if (blockSignature.numberOfArguments > 1) { const char *blockType = [blockSignature getArgumentTypeAtIndex:1]; if (blockType[0] != '@') { signaturesMatch = NO; } }

先比较艺术具名的参数个数是或不是等于,不等一定是不包容,signaturesMatch = NO。假诺参数个数相等,再比较我们要替换的措施里面第二个参数是否_cmd,对应的Type正是@,假若不是,也是不相称,所以signaturesMatch = NO。如若地点两条都满意,signaturesMatch = YES,那么就步向上面越发狂暴的对照。

 if (signaturesMatch) { for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx  ) { const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; // Only compare parameter, not the optional type data. if (!methodType || !blockType || methodType[0] != blockType[0]) { signaturesMatch = NO; break; } } }

此处循环也是从2始发的。比释迦牟尼评释为啥从第二人伊始相比。依旧用事前的例证。

[UIView aspect_hookSelector:@selector(UIView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspects, UIView *view, NSInteger index) { NSLog(@"按钮点击了 %ld",index); } error:nil];

此地本身要替换的原方法是UIView:atIndex:,那么相应的Type是v@:@q。依照上边的剖析,这里的blockSignature是在此之前调用调换出来的Type,应该是v@?@"<AspectInfo>"@"UIView"q。

argument return value 0 1 2 3
methodSignature v @ : @ q
blockSignature v @? @"<AspectInfo>" @"UIView" q

methodSignature 和 blockSignature 的return value都以void,所以对应的都以v。methodSignature的argument 0 是含有参数 self,所以对应的是@。blockSignature的argument 0 是block,所以对应的是@?。methodSignature的argument 1 是带有参数 _cmd,所以对应的是:。blockSignature的argument 1 是<AspectInfo>,所以对应的是@"<AspectInfo>"。从argument 2带头才是方法具名前面包车型大巴附和恐怕现身差距,需求比较的参数列表。

最后

 if (!signaturesMatch) { NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature]; AspectError(AspectErrorIncompatibleBlockSignature, description); return NO; }

借使由此地点的可比signaturesMatch都为NO,那么就抛出error,Block无法合作方法具名。

 AspectIdentifier *identifier = nil; if (blockSignature) { identifier = [AspectIdentifier new]; identifier.selector = selector; identifier.block = block; identifier.blockSignature = blockSignature; identifier.options = options; identifier.object = object; // weak } return identifier;

尽管这里分外成功了,就可以blockSignature全部都赋值给AspectIdentifier。那也正是为什么AspectIdentifier里面有一个独门的质量NSMethodSignature的原因。

AspectIdentifier还会有此外贰个方式invokeWithInfo。

 // Be extra paranoid. We already check that on hook registration. if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { AspectLogError(@"Block has too many arguments. Not calling %@", info); return NO; }

表明也写清楚了,那一个剖断是性心理障碍病人写的,到了此处block里面包车型地铁参数是不会超越原始方法的方法签字里面参数的个数的。

 // The `self` of the block will be the AspectInfo. Optional. if (numberOfArguments > 1) { [blockInvocation setArgument:&info atIndex:1]; }

把AspectInfo存入到blockInvocation中。

 void *argBuf = NULL; for (NSUInteger idx = 2; idx < numberOfArguments; idx  ) { const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; NSUInteger argSize; NSGetSizeAndAlignment(type, &argSize, NULL); if (!(argBuf = reallocf(argBuf, argSize))) { AspectLogError(@"Failed to allocate memory for block invocation."); return NO; } [originalInvocation getArgument:argBuf atIndex:idx]; [blockInvocation setArgument:argBuf atIndex:idx]; } [blockInvocation invokeWithTarget:self.block];

这一段是循环把originalInvocation中抽取参数,赋值到argBuf中,然后再赋值到blockInvocation里面。循环从2从头的由来上边已经说过了,这里不再赘言。最终把self.block赋值给blockInvocation的Target。

图片 13

总括,AspectIdentifier是一个切开Aspect的具体内容。里面会蕴藏了单个的 Aspect 的求实新闻,蕴涵实行时机,要施行 block 所要求选拔的有声有色新闻:包含方法具名、参数等等。初步化AspectIdentifier的经超过实际质是把大家传入的block打包成AspectIdentifier。

AspectsContainer

// Tracks all aspects for an object/class.@interface AspectsContainer : NSObject- addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;- removeAspect:aspect;- hasAspects;@property (atomic, copy) NSArray *beforeAspects;@property (atomic, copy) NSArray *insteadAspects;@property (atomic, copy) NSArray *afterAspects;@end

二个目标也许类的有着的 Aspects 全部景况,注意这里数组是经过atomic修饰的。关于atomic亟待注目的在于暗许情形下,由编写翻译器所合成的方法会通过锁定机制保险其原子性(atomicity)。要是属性具有nonatomic特质,则没有必要同步锁。

介意一共有两中容器,一个是目标的切丝,一个是类的切块。

5. AspectsContainer

图片 14

// Tracks all aspects for an object/class.@interface AspectsContainer : NSObject- addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;- removeAspect:aspect;- hasAspects;@property (atomic, copy) NSArray *beforeAspects;@property (atomic, copy) NSArray *insteadAspects;@property (atomic, copy) NSArray *afterAspects;@end

对应促成

#pragma mark - AspectsContainer@implementation AspectsContainer- hasAspects { return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;}- addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options { NSParameterAssert; NSUInteger position = options&AspectPositionFilter; switch  { case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break; case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break; case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break; }}- removeAspect:aspect { for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)), NSStringFromSelector(@selector(insteadAspects)), NSStringFromSelector(@selector(afterAspects))]) { NSArray *array = [self valueForKey:aspectArrayName]; NSUInteger index = [array indexOfObjectIdenticalTo:aspect]; if (array && index != NSNotFound) { NSMutableArray *newArray = [NSMutableArray arrayWithArray:array]; [newArray removeObjectAtIndex:index]; [self setValue:newArray forKey:aspectArrayName]; return YES; } } return NO;}- (NSString *)description { return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects];}@end

AspectsContainer相比较好通晓。addAspect会依照切面包车型大巴空子分别把切开Aspects放到对应的数组里面。removeAspects会循环移除全体的Aspects。hasAspects决断是不是有Aspects。

AspectsContainer是三个目的只怕类的具有的 Aspects 的器皿。全部会有三种容器。

值得大家注意的是这里数组是通过Atomic修饰的。关于Atomic须求小心在默许景况下,由编写翻译器所合成的方法会通过锁定机制确定保证其原子性(AtomicityState of Qatar。假如属性具有nonatomic特质,则无需同步锁。

AspectIdentifier

// Tracks a single aspect.@interface AspectIdentifier : NSObject  (instancetype)identifierWithSelector:selector object:object options:(AspectOptions)options block:block error:(NSError **)error;- invokeWithInfo:(id<AspectInfo>)info;@property (nonatomic, assign) SEL selector;@property (nonatomic, strong) id block;@property (nonatomic, strong) NSMethodSignature *blockSignature;@property (nonatomic, weak) id object;@property (nonatomic, assign) AspectOptions options;@end

一个Aspect的具体内容。首要饱含了单个的 aspect 的实际消息,包含实践机缘,要试行 block 所要求采纳的求实消息:包含方法签字、参数等等。其实便是将大家传入的bloc,包装成AspectIdentifier,便于后续使用。由此大家更迭的block实例化。也等于将大家传入的block,包装成了AspectIdentifier

6. AspectTracker

图片 15

@interface AspectTracker : NSObject- initWithTrackedClass:trackedClass;@property (nonatomic, strong) Class trackedClass;@property (nonatomic, readonly) NSString *trackedClassName;@property (nonatomic, strong) NSMutableSet *selectorNames;@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;- addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;- removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;- subclassHasHookedSelectorName:(NSString *)selectorName;- subclassTrackersHookingSelectorName:(NSString *)selectorName;@end

对应促成

@implementation AspectTracker- initWithTrackedClass:trackedClass { if (self = [super init]) { _trackedClass = trackedClass; _selectorNames = [NSMutableSet new]; _selectorNamesToSubclassTrackers = [NSMutableDictionary new]; } return self;}- subclassHasHookedSelectorName:(NSString *)selectorName { return self.selectorNamesToSubclassTrackers[selectorName] != nil;}- addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName { NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName]; if (!trackerSet) { trackerSet = [NSMutableSet new]; self.selectorNamesToSubclassTrackers[selectorName] = trackerSet; } [trackerSet addObject:subclassTracker];}- removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName { NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName]; [trackerSet removeObject:subclassTracker]; if (trackerSet.count == 0) { [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName]; }}- subclassTrackersHookingSelectorName:(NSString *)selectorName { NSMutableSet *hookingSubclassTrackers = [NSMutableSet new]; for (AspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) { if ([tracker.selectorNames containsObject:selectorName]) { [hookingSubclassTrackers addObject:tracker]; } [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]]; } return hookingSubclassTrackers;}- (NSString *)trackedClassName { return NSStringFromClass(self.trackedClass);}- (NSString *)description { return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];}@end

AspectTracker那些类是用来追踪要被hook的类。trackedClass是被追踪的类。trackedClassName是被追踪类的类名。selectorNames是一个NSMutableSet,这里会记录要被hook替换的办法名,用NSMutableSet是为着制止重复替换方法。selectorNamesToSubclassTrackers是叁个辞书,key是hookingSelectorName,value是装满AspectTracker的NSMutableSet。

addSubclassTracker方法是把AspectTracker到场到相应selectorName的集结中。removeSubclassTracker方法是把AspectTracker从对应的selectorName的聚合中移除。subclassTrackersHookingSelectorName方法是一个并查集,传入四个selectorName,通过递归查找,找到全体包含那些selectorName的set,最后把这个set归并在联同盟为重临值重返。

图片 16

Aspects 库中就多少个函数,八个是针对类的,两个是本着实例的。

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

七个办法的贯彻都以调用同三个方法aspect_add,只是传入的参数分歧而已。所以大家要是从aspect_add起初切磋就能够。

- aspect_hookSelector:selector withOptions:(AspectOptions)options usingBlock:block error:(NSError **)error└── aspect_add(self, selector, options, block, error); └── aspect_performLocked ├── aspect_isSelectorAllowedAndTrack └── aspect_prepareClassAndHookSelector

那是函数调用栈。从aspect_add开首钻探。

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { NSCParameterAssert; NSCParameterAssert; NSCParameterAssert; __block AspectIdentifier *identifier = nil; aspect_performLocked(^{ if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { [aspectContainer addAspect:identifier withOptions:options]; // Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error); } } }); return identifier;}

aspect_add函数共计5个入参,第叁个参数是self,selector是外围传进来必要hook的SEL,options是切丝的时间,block是切丝的推行措施,最终的error是荒谬。

aspect_performLocked是叁个自旋锁。自旋锁是作用相比高的一种锁,比较@synchronized来讲功用高得多。

static void aspect_performLocked(dispatch_block_t block) { static OSSpinLock aspect_lock = OS_SPINLOCK_INIT; OSSpinLockLock(&aspect_lock); block(); OSSpinLockUnlock(&aspect_lock);}

假定对iOS中8大锁不打听的,可以看以下两篇小说

iOS 管见所及知识点:Lock 深刻理解 iOS 开拓中的锁

图片 17

唯独自旋锁也许有望现身难点的:如若叁个低优先级的线程获得锁并访谈分享能源,此时二个高优先级的线程也尝尝取得这一个锁,它会处于 spin lock 的忙等(busy-waitState of Qatar状态进而占用一大波CPU。当时低优先级线程不或许与高优先级线程争夺 CPU 时间,进而形成任务迟迟完不成、相当的小概自由 lock。不再安全的 OSSpinLock

OSSpinLock的主题材料在于,要是访谈那些所的线程不是如出一辙优先级的话,会有死锁的心腹危害。

那边如今感觉是相近优先级的线程,所以OSSpinLock保险了线程安全。也正是说aspect_performLocked是保卫安全了block的线程安全。

今昔就剩下aspect_isSelectorAllowedAndTrack函数和aspect_prepareClassAndHookSelector函数了。

接下去先看看aspect_isSelectorAllowedAndTrack函数完成进程。

 static NSSet *disallowedSelectorList; static dispatch_once_t pred; dispatch_once(&pred, ^{ disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil]; });

先定义了三个NSSet,那之中是三个“黑名单”,是不准hook的函数名。retain, release, autorelease, forwardInvocation:是不一样意被hook的。

 NSString *selectorName = NSStringFromSelector; if ([disallowedSelectorList containsObject:selectorName]) { NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName]; AspectError(AspectErrorSelectorBlacklisted, errorDescription); return NO; }

当检测到selector的函数名是黑名单之中的函数名,马上报错。

 AspectOptions position = options&AspectPositionFilter; if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) { NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc."; AspectError(AspectErrorSelectorDeallocPosition, errorDesc); return NO; }

双重检查要是要切开dealloc,切块时间只好在dealloc早前,要是否AspectPositionBefore,也要报错。

 if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) { NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName]; AspectError(AspectErrorDoesNotRespondToSelector, errorDesc); return NO; }

当selector不在黑名单里面了,若是切成条是dealloc,且selector在其事情发生以前了。这个时候就该论断该办法是不是存在。假诺self和self.class里面都找不到该selector,会报错找不到该方法。

 if (class_isMetaClass(object_getClass { Class klass = [self class]; NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); Class currentClass = [self class]; AspectTracker *tracker = swizzledClassesDict[currentClass]; if ([tracker subclassHasHookedSelectorName:selectorName]) { NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName]; NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"]; NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames]; AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); return NO; }

class_isMetaClass 先判别是还是不是元类。接下来的判定都以剖断元类里面是还是不是允许被替换方法。

subclassHasHookedSelectorName会推断当前tracker的subclass里面是还是不是包括selectorName。因为二个艺术在四个类的层级里面只好被hook三次。假如已经tracker里面已经满含了一回,那么会报错。

 do { tracker = swizzledClassesDict[currentClass]; if ([tracker.selectorNames containsObject:selectorName]) { if (klass == currentClass) { // Already modified and topmost! return YES; } NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)]; AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); return NO; } } while ((currentClass = class_getSuperclass(currentClass)));

在这个do-while循环中,currentClass = class_getSuperclass(currentClass卡塔尔国那一个判别会从currentClass的superclass早前,一贯往上找,直到那个类为根类NSObject。

 currentClass = klass; AspectTracker *subclassTracker = nil; do { tracker = swizzledClassesDict[currentClass]; if  { tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass]; swizzledClassesDict[(id<NSCopying>)currentClass] = tracker; } if (subclassTracker) { [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName]; } else { [tracker.selectorNames addObject:selectorName]; } // All superclasses get marked as having a subclass that is modified. subclassTracker = tracker; }while ((currentClass = class_getSuperclass(currentClass)));

透过地点合法性hook决断和类形式不容许再度替换的检讨后,到此,就足以把要hook的音讯记录下来,用AspectTracker标志。在标志进程中,一旦子类被转移,父类也急需随着一块被标识。do-while的停下条件依然currentClass = class_getSuperclass(currentClass)。

如上是元类的类模式hook推断合法性的代码。

假若不是元类,只要不是hook那"retain", "release", "autorelease", "forwardInvocation:"4种方法,并且hook “dealloc”方法的机缘必需是before,并且selector能被找到,那么方法就足以被hook。

因而了selector是或不是能被hook合法性的自己商酌之后,将在获取或然创设AspectsContainer容器了。

// Loads or creates the aspect container.static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) { NSCParameterAssert; SEL aliasSelector = aspect_aliasForSelector; AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector); if (!aspectContainer) { aspectContainer = [AspectsContainer new]; objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN); } return aspectContainer;}

在读取或许创立AspectsContainer以前,第一步是先标识一下selector。

static SEL aspect_aliasForSelector(SEL selector) { NSCParameterAssert; return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector]);}

在全局代码里面定义了一个常量字符串

static NSString *const AspectsMessagePrefix = @"aspects_";

用那么些字符串标志全数的selector,都加上前缀"aspects_"。然后拿走其对应的AssociatedObject关联对象,纵然得到不到,就创办二个关乎对象。最后获得selector有"aspects_"前缀,对应的aspectContainer。

得到了aspectContainer之后,就可以带头构思大家要hook方法的一对新闻。那几个新闻都装在AspectIdentifier中,所以大家需求新建三个AspectIdentifier。

调用AspectIdentifier的instancetype方法,创立三个新的AspectIdentifier

  (instancetype)identifierWithSelector:selector object:object options:(AspectOptions)options block:block error:(NSError **)error

这些instancetype方法,独有一种情形会创立失利,那正是aspect_isCompatibleBlockSignature方法重回NO。重返NO就意味着,大家要替换的方法block和要替换的原方法,两个的点子具名是不适合的。(这么些函数在上头精解过了,这里不再赘言)。方法签字相配成功之后,就能够创建好三个AspectIdentifier。

[aspectContainer addAspect:identifier withOptions:options];

aspectContainer容器会把它出席到容器中。达成了容器和AspectIdentifier开端化之后,就能够起来思忖展开hook了。通过options选项分别增添到容器中的beforeAspects,insteadAspects,afterAspects那七个数组

// Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error);

计算一下,aspect_add干了一部分怎么着思谋干活:

  1. 先是调用aspect_performLocked ,利用自旋锁,有限帮衬一切操作的线程安全
  2. 随后调用aspect_isSelectorAllowedAndTrack对传进来的参数进行强校验,保险参数合法性。
  3. 随着成立AspectsContainer容器,利用AssociatedObject关联对象动态增加到NSObject分类中作为质量的。
  4. 再由入参selector,option,创设AspectIdentifier实例。AspectIdentifier首要包含了单个的 Aspect的实际音信,满含实践时机,要施行block 所要求采取的切实新闻。
  5. 再将单个的 AspectIdentifier 的切切实实消息加到属性AspectsContainer容器中。通过options选项分别增加到容器中的beforeAspects,insteadAspects,afterAspects那多个数组。
  6. 末尾调用prepareClassAndHookSelector打算hook。

图片 18

是因为简书单篇小说字数约束,无语只可以拆成2篇,下有个别见下篇。

AspectInfo

@interface AspectInfo : NSObject <AspctInfo>- initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;@property (nonatomic, unsafe_unretained, readonly) id instance;@property (nonatomic, strong, readonly) NSArray *arguments;@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;@end

主要是 NSInvocation 信息。将NSInvocation包装一层,举个例子参数新闻等。便于直接行使。

AspectTracker

@interface AspectTracker : NSObject- initWithTrackedClass:trackedClass parent:(AspectTracker *)parent;@property (nonatomic, strong) Class trackedClass;@property (nonatomic, strong) NSMutableSet *selectorNames;@property (nonatomic, weak) AspectTracker *parentEntry;@end

用于追踪所改动的类,打上标识,用于替换类方法,制止再次替换类方法

流程

图片 19

__Block的使用

做过iOS的都知情,__BlockARCXMRC情状修饰对象下是例外的。具体的从头到尾的经过能够看看本人的另一篇小说。这里只交给结论:

ARC环境下,__Block会对修饰的指标强援用,在MRC条件下对修饰的靶子不会强援用。并且__block修饰局地变量,表示这几个目的是能够在block里头纠正,假诺不这么写,会报Variable is not assignable (missing__block type specifier)的错误。

来会见代码:

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { NSCParameterAssert; NSCParameterAssert; NSCParameterAssert; __block AspectIdentifier *identifier = nil; aspect_performLocked(^{ if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { [aspectContainer addAspect:identifier withOptions:options]; // Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error); } } }); return identifier;}

小心这里的__block AspectIdentifier *identifier = nil;怎么要这么写啊。上面已经说过得很领悟。因为identifier是有的变量,若是不加__block修饰,block里面不可能改正identifier

自旋锁(OSSpinLockLock)

Aspect是线程安全的,那么它是经过哪些艺术办到的吧。假若您对iOS中的多少个锁不了然,可以看看自身的另一篇小说,里面有介绍。

自旋锁是功用相比高的一种锁,相比较@synchronized来说作用高得多。不过要求当心,如若访谈这么些所的线程不是相符优先级的话,会有死锁的秘密风险。具体原因请看YYKit笔者博客。

static void aspect_performLocked(dispatch_block_t block) { static OSSpinLock aspect_lock = OS_SPINLOCK_INIT; OSSpinLockLock(&aspect_lock); // 加锁执行block block(); // 执行完之后释放锁 OSSpinLockUnlock(&aspect_lock);}

透过如此的加锁情势,所以Aspect作者说它是线程安全的。

进行的输入

 aspect_performLocked(^{ if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { [aspectContainer addAspect:identifier withOptions:options]; // Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error); } } });

上边那句代码是实行Aspect的入口。看是轻便其实当中有非常复杂的地点。

看一看block做了何等事情。

  • 1.对出入进来的参数实行稽查,保障参数合法
  • 2.成立aspect容器,注意容器是懒加载格局动态增加到NSObject分拣中作为质量。
  • 3.依照参数,举个例子selector,option,创建AspectIdentifier实例,上边已经说过AspectIdentifier尤为重要包罗了单个的 Aspect的实际音讯,包涵施行机会,要履行block 所必要动用的具体音讯。
  • 4.将单个的 Aspect 的实际音信加到属性aspectContainer
  • 5.十二万分根本的一对进展Hook操作,生成子类,类型编码管理,方法替换等。

上边就对地点5个部分各自留神的分析。

参数考验

严峻来说,在调用每一个办法的时候都亟待对传播进来的参数做二回校验。极其是在做SDK的时候,因为你一贯不知晓外面传进来的是如何数据,到底是或不是为空,数据类型是还是不是科学。经常付出的经过中,由于大家都领悟传入的参数大部分来讲都是我们复合预期的所以就从不做怎么样核准专业。

重返Aspect中。核准的剧情珍贵有如下多少个:

  • 1.Swizzle了不能Swizzle的方法,比如@retain", @"release", @"autorelease", @"forwardInvocation:":借使替换了这么的方法,大家是不可能成功开展Swizzle的。
  • 2.无胫而行的实施机缘是或不是正确,举个例子对于dealloc方法,`Swizzle之能在事情发生前进行调用。
  • 3.对象大概类是不是响应传入的selector
  • 4.假设替换的是类措施,则举行是还是不是再度替换的反省

此间根本捋一捋,类方式的查实参数,为了查验类的不二秘籍只可以修正一次。

 if (class_isMetaClass(object_getClass { Class klass = [self class]; NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); Class currentClass = [self class]; do { AspectTracker *tracker = swizzledClassesDict[currentClass]; if ([tracker.selectorNames containsObject:selectorName]) { // Find the topmost class for the log. if (tracker.parentEntry) { AspectTracker *topmostEntry = tracker.parentEntry; while (topmostEntry.parentEntry) { topmostEntry = topmostEntry.parentEntry; } NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)]; AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); return NO; }else if (klass == currentClass) { // Already modified and topmost! return YES; } } }while ((currentClass = class_getSuperclass(currentClass))); // Add the selector as being modified. currentClass = klass; AspectTracker *parentTracker = nil; do { AspectTracker *tracker = swizzledClassesDict[currentClass]; if  { tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker]; swizzledClassesDict[(id<NSCopying>)currentClass] = tracker; } [tracker.selectorNames addObject:selectorName]; // All superclasses get marked as having a subclass that is modified. parentTracker = tracker; }while ((currentClass = class_getSuperclass(currentClass))); }
  • 怎样推断传入的是类实际不是指标:class_isMetaClass(object_getClassobject_getClass是赢稳妥前指标由什么实例化。

类方式只可以替换一遍,是在一切类的存在延续树上将验,而不只是独自的三个类,从上边代码能够见到:

 do { AspectTracker *tracker = swizzledClassesDict[currentClass]; if ([tracker.selectorNames containsObject:selectorName]) { // Find the topmost class for the log. if (tracker.parentEntry) { AspectTracker *topmostEntry = tracker.parentEntry; while (topmostEntry.parentEntry) { topmostEntry = topmostEntry.parentEntry; } NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)]; AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); return NO; }else if (klass == currentClass) { // Already modified and topmost! return YES; } } }while ((currentClass = class_getSuperclass(currentClass)));

在乎那句(currentClass = class_getSuperclass(currentClass)当且唯有当那几个类为根类的时候才不会一而再循环查找。

再来看看这段:

currentClass = klass; AspectTracker *parentTracker = nil; do { AspectTracker *tracker = swizzledClassesDict[currentClass]; if  { tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker]; swizzledClassesDict[(id<NSCopying>)currentClass] = tracker; } [tracker.selectorNames addObject:selectorName]; // All superclasses get marked as having a subclass that is modified. parentTracker = tracker; }while ((currentClass = class_getSuperclass(currentClass)));

这段的效应正是尽管类被涂改了,给其父类打上标识。然后结合地点的决断是还是不是再一次替换。这里为什么要用父类呢。以此runtime类与父类,根类关系有关为了使得的遍历,必要找到一个退出的规格,而退出的口径,结合到runtime就是根类未有父类。那正是退出的尺度。

只要还还没弄懂,回去看看runtime的基本知识

创建Aspect容器

AspectsContainer的机能是为着保存整个Apects的动静,包括充分/删除的aspect,根据试行的机缘(before,instead,after)而保留的兼具的aspcet。当中使用了比较常用的动态增加属性。

static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) { NSCParameterAssert; // 得到新的SEL(加上了前缀aspects_)并新增为属性 SEL aliasSelector = aspect_aliasForSelector; // 得到和aliasSelector相对应的AspectsContainer AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector); if (!aspectContainer) { aspectContainer = [AspectsContainer new]; objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN); } return aspectContainer;}

这一步照旧比较轻松。

单个的 Aspect 的切实可行消息AspectIdentifier

下边介绍的AspectsContainer中间装的具体的东东正是AspectIdentifierAspectIdentifier包罗的是宛在近日到每二个aspect的现实消息,直接看属性:

@property (nonatomic, assign) SEL selector;@property (nonatomic, strong) id block;@property (nonatomic, strong) NSMethodSignature *blockSignature;@property (nonatomic, weak) id object; // 具体信息所属类,用weak@property (nonatomic, assign) AspectOptions options;

开首化方法将索要的诸如:selblockoption,传进去。`

  • (instancetype)identifierWithSelector:selector object:object options:(AspectOptions)options block:block error:(NSError **)error`

急需器重注意方式具名NSMethodSignature,因为我们在运用Aspect的时候是直接通过block来替换方法的,所以供给将我们传入的block改动为现实的法门。这也是干什么会在AspectIdentifier中多三个艺术具名的质量。

在介绍方法具名此前必要对block切切实实的组织有必然领会。在Aspect中,大家定义了二个组织体用来替代系统的block。总体构造其实和系统的等同。

// 模仿系统的block结构typedef struct _AspectBlock { __unused Class isa; AspectBlockFlags flags; __unused int reserved; void (__unused *invoke)(struct _AspectBlock *block, ...); struct { unsigned long int reserved; unsigned long int size; // requires AspectBlockFlagsHasCopyDisposeHelpers void (void *dst, const void *src); void (const void *); // requires AspectBlockFlagsHasSignature const char *signature; const char *layout; } *descriptor; // imported variables} *AspectBlockRef;

看一看将block转为艺术具名的代码:

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) { // 将block转换为自定义的形式 AspectBlockRef layout = (__bridge void *)block; // 过滤 if (!(layout->flags & AspectBlockFlagsHasSignature)) {// flags不是AspectBlockFlagsHasSignature类型 NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block]; AspectError(AspectErrorMissingBlockSignature, description); return nil; } void *desc = layout->descriptor; desc  = 2 * sizeof(unsigned long int); if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) { desc  = 2 * sizeof; } if  { NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block]; AspectError(AspectErrorMissingBlockSignature, description); return nil; } const char *signature = (*(const char **)desc); // Returns an NSMethodSignature object for the given Objective-C method type string. // 根据类型编码返回真正方法签名 return [NSMethodSignature signatureWithObjCTypes:signature];}

block是作为id品类进行传递的,何况是全局类型的block。假如调换来功,layout个中都会有对应的值。

这句话const char *signature = (*(const char **)desc);获取大家传入block的体系编码。通过品种编码取得block所对应的方法具名。

此地大约给出再次来到值为空,无参数的block转换之后的措施具名的布局:

<NSMethodSignature: 0x7f9219d064e0> number of arguments = 1 frame size = 224 is special struct return? NO return value: -------- -------- -------- -------- type encoding  'v' flags {} modifiers {} frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0} memory {offset = 0, size = 0} argument 0: -------- -------- -------- -------- type encoding  '@?' flags {isObject, isBlock} modifiers {} frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} memory {offset = 0, size = 8}

获得了章程具名,供给对那几个方式具名和替换实际措施举行相比。自此处可以体会到做叁个第三方对参数的检验是格外可怜关键的。那也是大家日常在使用开垦中所欠缺的。

这怎么和原先的点子相比呢。相比间接的主意正是,取得替换方法的诀要具名和大家将block转移之后的不二秘诀具名相比。

直白看见代码:

// 原方法签名 NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector]; // 参数不匹配 if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) { signaturesMatch = NO; }else { if (blockSignature.numberOfArguments > 1) { // blockSignature参数没有_cmd, const char *blockType = [blockSignature getArgumentTypeAtIndex:1]; // 类型编码 if (blockType[0] != '@') { signaturesMatch = NO; } } // Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2. // 对于block来说 // The block can have less arguments than the method, that's ok. if (signaturesMatch) { for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx  ) { const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; // Only compare parameter, not the optional type data. // 参数匹配 if (!methodType || !blockType || methodType[0] != blockType[0]) { signaturesMatch = NO; break; } } } } if (!signaturesMatch) { NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature]; AspectError(AspectErrorIncompatibleBlockSignature, description); return NO; }

说一说为何要从第一个参数最初相比呢。首先大家周围的章程都有连个隐敝的参数,一个是__cmd,一个是self。而艺术具名的参数也可以有八个(针对二个再次回到值为空,参数为空的办法)。第一个是self,第叁个是SEL。源代码有连锁表达。

本人直接打字与印刷一下:

  • methodSignature:

  • <NSMethodSignature: 0x7f9219d0cab0>number of arguments = 2frame size = 224is special struct return? NOreturn value: -------- -------- -------- --------type encoding 'B'flags {}modifiers {}frame {offset = 0, offset adjust = 0, size = 8, size adjust = -7}memory {offset = 0, size = 1}argument 0: -------- -------- -------- --------type encoding '@'flags {isObject}modifiers {}frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}argument 1: -------- -------- -------- --------type encoding ':'flags {}modifiers {}frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}

  • blockSignature

<NSMethodSignature: 0x7f9219d064e0>number of arguments = 1frame size = 224is special struct return? NOreturn value: -------- -------- -------- --------type encoding 'v'flags {}modifiers {}frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}memory {offset = 0, size = 0}argument 0: -------- -------- -------- --------type encoding '@?'flags {isObject, isBlock}modifiers {}frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}argument 1: -------- -------- -------- --------type encoding ':'flags {}modifiers {}frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}

将`block`转换为方法签名之后就可以给`AspectIdentifier`具体属性赋值了。

AspectIdentifier *identifier = nil;if (blockSignature) {identifier = [AspectIdentifier new];identifier.selector = selector;identifier.block = block;identifier.blockSignature = blockSignature;identifier.options = options;identifier.object = object; // weak}

### 将具体的`AspectIdentifier `添加到容器中这一步比较简单`[aspectContainer addAspect:identifier withOptions:options];`### 方法替换方法替换是这个库最为核心的部分,也是最难的部分。里面的思路以及方法很多都值得学习。这部分也是自己花了很多时间去了解。大致的步骤:- 1.创建子类,`hook`子类- 2.处理实现过`MsgForwardIMP`的类或者对象- 3.将需要替换的`selector`,指向`_objc_msgForward`来具体看一看#### 创建子类创建子类将`runtime`用到了极致。其实我们所做的替换完全是在运行时动态创建子类的时候实现的。这样对原来替换的类或者对象没有任何影响而且可以在子类基础上新增或者删除`aspect`。注意`Class statedClass = self.class;`和`Class baseClass = object_getClass;`的区别,前者获取类对象,后者获取本类是由什么实例化。**所有的操作是在类对象基础上操作的,而不是一个对象**最为重要的就是`aspect_swizzleClassInPlace`方法

static Class aspect_swizzleClassInPlace(Class klass) {NSCParameterAssert;NSString *className = NSStringFromClass;

// 1.保证线程安全,2保证数组单例_aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { // 不包含这个类,如果已经包含 if (![swizzledClasses containsObject:className]) { /** 深藏的核心部分 **/ // 将原IMP指向forwardInvocation aspect_swizzleForwardInvocation; // 添加进来 [swizzledClasses addObject:className]; }});return klass;

}

为了保证线程安全,所以用了`_aspect_modifySwizzledClasses`是单例方法。将传入的`block`安全执行

static void _aspect_modifySwizzledClasses(void (NSMutableSet *swizzledClasses)) {static NSMutableSet *swizzledClasses;static dispatch_once_t pred;dispatch_once(&pred, ^{swizzledClasses = [NSMutableSet new];});@synchronized(swizzledClasses) {block(swizzledClasses);}}

来看第一个重头戏:**aspect_swizzleForwardInvocation**这个方法的目的就是将本类中的`forwardInvocation`方法替换为我们自定义的`__ASPECTS_ARE_BEING_CALLED__`方法。这样只要类没有找到消息处理着都会走到自定义的`__ASPECTS_ARE_BEING_CALLED__ `。这样就可以统一处理了。这里有个小小的问题,如果`forwardInvocation `已经被替换了,那么就需要特殊处理。比如这里判断的方法是,如果原来实现过`forwardInvocation `。则新增一个方法`AspectsForwardInvocationSelectorName`,指向`originalImplementation `。这个时候已经`forwardInvocation`已经指向了我们自定义的`AspectsForwardInvocationSelectorName `。所以是同理的。

IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), ASPECTS_ARE_BEING_CALLED, "v@:@");

if (originalImplementation) { // 将__aspects_forwardInvocation:指向originalImplementation, // 将originalImplementation添加到Method,以便下次调用,直接就可以了 class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");}

再来看看`__ASPECTS_ARE_BEING_CALLED__`这个方法如何将转发过来的参数成功的转换为我们需要的参数。

static void ASPECTS_ARE_BEING_CALLED(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {NSCParameterAssert;NSCParameterAssert(invocation);

SEL originalSelector = invocation.selector;// 加前缀SEL aliasSelector = aspect_aliasForSelector(invocation.selector);invocation.selector = aliasSelector;// 本对象的AspectsContainer,添加到对象的aspectAspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);// 这个类的AspectsContainer,添加类上面的aspectAspectsContainer *classContainer = aspect_getContainerForClass(object_getClass, 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;}// 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]) { (id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation); }else { [self doesNotRecognizeSelector:invocation.selector]; }}// Remove any hooks that are queued for deregistration.//  
		

本文由新葡京8455发布,转载请注明来源

关键词: