面向切面编程之,源码学习

作者:计算机网络

图片 1

1、面向切面编程应用在统计上
业务逻辑和统计逻辑经常耦合在一起,一方面影响了正常的业务逻辑,同时也很容易搞乱打点逻辑,而且要查看打点情况的时候也很分散。在 web 编程时候,这种场景很早就有了很成熟的方案,也就是所谓的AOP 编程(面向切面编程),其原理也就是在不更改正常的业务处理流程的前提下,通过生成一个动态代理类,从而实现对目标对象嵌入附加的操作。在 iOS 中,要想实现相似的效果也很简单,利用 oc 的动态性,通过 swizzling method 改变目标函数的 selector 所指向的实现,然后在新的实现中实现附加的操作,完成之后再回到原来的处理逻辑。
开源框架Aspects是一个非常好的框架。
Aspects

AOP 面向切面编程,在对于埋点、日志记录等操作来说是一个很好的解决方案。而 Aspects 是一个对于AOP编程的一个优雅的实现,也可以直接借助这个库来使用AOP思想。需要值得注意的是,Aspects   是通过消息转发机制的最后一个阶段 ForwardInvocation 来实现的,为了性能,所以这里不要频繁的调用。  github:

简介

Aspects是一个轻量的面向切面编程(AOP)的第三方库,面向切面编程简单来说,就是在原来的业务流程之中的某个位置插入额外的功能,不影响之前的逻辑。举个栗子来说,比如项目的统计埋点,统计页面进入的次数,我们一般是hook@selector(viewWillAppear:)方法,然后加入埋点上报,这就相当于我们在viewWillAppear中插入了一个切面来附加额外的功能。

先看看函数调用栈的情况

2、基本原理

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

Aspects

先来看下头文件,头文件中只有两个方法

  (id<AspectToken>)aspect_hookSelector:(SEL)selector
                       withOptions:(AspectOptions)options
                        usingBlock:(id)block
                             error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

方法表示hook对类的所有实例对象生效(注意不是hook类方法),-方法表示对类的单个实例对象生效,另外Aspects不支持hook类方法。

进入.m文件查看实现,两个方法都是调用了同一个c函数aspect_add();

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

                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}   

aspect_performLocked函数是给block函数的执行进行加锁处理,aspect_isSelectorAllowedAndTrack()判断是否允许hook该方法。

- aspect_prepareClassAndHookSelector(self, selector, error); ├── aspect_hookClass(self, error) │ ├──aspect_swizzleClassInPlace │ ├──aspect_swizzleForwardInvocation │ │ └──__ASPECTS_ARE_BEING_CALLED__ │ │ ├──aspect_aliasForSelector │ │ ├──aspect_getContainerForClass │ │ ├──aspect_invoke │ │ └──aspect_remove │ └── aspect_hookedGetClass ├── aspect_isMsgForwardIMP ├──aspect_aliasForSelector └── aspect_getMsgForwardIMP

原理1.png

  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

aspect_isSelectorAllowedAndTrack

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
    static NSSet *disallowedSelectorList;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        //设置hook黑名单
        disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
    });

    //检查黑名单,retain,release,autorelease,forwardInvocation方法不允许hook
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

    //针对dealloc方法的hook有额外要求,插入的block代码只能选择在dealloc之后执行
    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;
    }
    //从![self.class instancesRespondToSelector:selector]可以看出是不能hook类方法的
    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;
    }

    //判断是否是元类,也即是判断self是类还是对象。若self是类(如UIViewController),进入if分支;若self是对象,则直接renturn YES。
    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        //全局字典,Class为key,value是AspectTracker对象
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];

        AspectTracker *tracker = swizzledClassesDict[currentClass];
        //判断子类是否已经hook了该方法,整个继承链上同一个方法只能hook一次
        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;
        }

        //这一步do-while作用与上面的if相同,都是为了保证一个继承链上只能有一次hook,但是两个的场景不同。上面的if判断针对的场景是:先hook子类的方法,再hook父类的同一方法,会从上面的if剔除父类的hook。do-while的场景是:先hook父类的方法,再hook子类的方法,会从这一步剔除子类的hook。注意:针对同一个类的方法hook多次是可以的
        do {
            tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {
                if (klass == currentClass) {
                    return YES;//可以hook同一个类的方法多次
                }
                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)));

        currentClass = klass;
        AspectTracker *subclassTracker = nil;
        //这一步的do-while是保存selector并且遍历父类,将所有的类都进行hook标记,保证整个继承链针对一个方法只能hook一次
        do {
            tracker = swizzledClassesDict[currentClass];
            if (!tracker) {
                tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
                swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
            }
            if (subclassTracker) {
                [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
            } else {
                [tracker.selectorNames addObject:selectorName];
            }
            subclassTracker = tracker;
        }while ((currentClass = class_getSuperclass(currentClass)));
    } else {
        //这里是对某个对象的hook,直接return YES
        return YES;
    }

    return YES;
}

额外补充一点,区分[self class]和object_getClass(self)的不同。如果self是对象,则这两个返回值一样,即self的对应的类;如果self是类,[self class]返回的还是这个类本身也就是self,而object_getClass(self)返回的是它的元类。

回到aspect_add()函数,继续往下看。

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

AspectsContainer是一个容器类,AspectIdentifier类用来整合hook信息的,比如hook的selector,传递进来的block,hook的对象等,然后将identifier保存在aspectContainer中,根据我们传递的参数options,存放在内部不同的数组中。这里重点注意这个方法

  (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
    NSCParameterAssert(block);
    NSCParameterAssert(selector);
    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); //生成block的签名
    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;
}

aspect_blockMethodSignature()将我们传递进来的block生成方法签名。

从调用栈可以看出,Aspects hook过程主要分4个阶段,hookClass,ASPECTS_ARE_BEING_CALLED,prepareClassAndHookSelector,remove。

每一个对象都有一个指向其所属类的isa指针,通过该指针找到所属的类,然后会在所属类中的方法列表中寻找方法的实现,如果在方法列表中查到了和选择子名称相符的方法就会跳转到他的方法实现,如果找不到会向其父类的方法列表中查找,以此类推,直到NSObject类,如果还是查找不到就会执行“消息转发”操作。
另外为了保证消息机制的效率,每一个类都设置一个缓存方法列表,缓存列表中包含了当前类的方法以及继承自父类的方法,在查询方法列的时候,都会先查询本类的缓存列表,再去查询方法类别。这样当一个方法已经被调用过一次,下次调用就会很快的查询到并调用。

   下面是我读源码的一些

aspect_blockMethodSignature

Aspects仿照OC自己定义了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 (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;

若想看系统中的block结构可以通过clang编译源文件,具体步骤可以看我之前的文章。可以看到结构体中有个变量const char *signature,这个就是block的签名串。

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(void *);
    }
    if (!desc) {
        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];
}

上面的代码主要就是拿到signature,主要思路是从block中的descriptor地址偏移字节取到signature。
生成block签名之后还要检测签名的完整性,主要是检测block的返回值,参数跟hook的方法的返回值和参数保持一致。

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    //对入参的selector生成方法签名
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];

    //判断block和selector的参数,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;
            }
        }
        //从第三个参数开始比对block和selector的参数是否一致
        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;
}

图片 2

方法调用的过程
1.在对象自己缓存的方法列表中去找要调用的方法,找到了就直接执行其实现。
2.缓存里没找到,就去上面说的它的方法列表里找,找到了就执行其实现。
3.还没找到,说明这个类自己没有了,就会通过isa去向其父类里执行1、2。
4.如果找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,我们可以自己在拦截调用方法里面做一些处理。
5.如果没有在拦截调用里做处理,那么就会报错崩溃。

 Aspects 的接口只有两个:

oc中的方法调用底层都是通过objc_msgSend()方法调用,这个方法默认传递两个参数,一个是调用的对象object(self),还有一个是@selector,任何一个OC的方法都有这两个参数,所以block和selector的比较从index

2的参数开始。比较参数比较的是参数的类型,如int类型就是i,SEL类型是:,OC对象是@,block是@?,更多的类型可以看苹果官方文档中关于typeEncoding的介绍。另外关于block的签名只有一个默认参数,第一个参数(index=0)是block本身,第二个参数Aspects约定是id<AspectInfo>类型的参数(为了和selector的签名参数保持一致),所以上面判断了block中第二个参数是否是id类型。

接下来aspect_prepareClassAndHookSelector()方法就是Aspects核心代码了。

1. hookClass
 NSCParameterAssert; Class statedClass = self.class; Class baseClass = object_getClass; NSString *className = NSStringFromClass(baseClass);

statedClass 和 baseClass是有区别的的。

Class object_getClass{ if  return obj->getIsa(); else return Nil;}  class { return self;}

statedClass 是获取类对象,baseClass是获取到类的isa。

 // Already subclassed if ([className hasSuffix:AspectsSubclassSuffix]) { return baseClass; // We swizzle a class object, not a single object. }else if (class_isMetaClass(baseClass)) { return aspect_swizzleClassInPlaceself); // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place. }else if (statedClass != baseClass) { return aspect_swizzleClassInPlace(baseClass); }

先判断是用来className是否包含hasSuffix:AspectsSubclassSuffix

static NSString *const AspectsSubclassSuffix = @"_Aspects_";

如果包含了@"_Aspects_"后缀,代表该类已经被hook过了,直接return。如果不包含@"_Aspects_"后缀,再判断是否是baseClass是否是元类,如果是元类,调用aspect_swizzleClassInPlace。如果也不是元类,再判断statedClass 和 baseClass是否相等,如果不相等,说明为KVO过的对象,因为KVO的对象isa指针会指向一个中间类。对KVO中间类调用aspect_swizzleClassInPlace。

static Class aspect_swizzleClassInPlace(Class klass) { NSCParameterAssert; NSString *className = NSStringFromClass; _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { if (![swizzledClasses containsObject:className]) { aspect_swizzleForwardInvocation; [swizzledClasses addObject:className]; } }); return klass;}

_aspect_modifySwizzledClasses会传入一个入参为(NSMutableSet *swizzledClasses)的block,block里面就是判断在这个Set里面是否包含当前的ClassName,如果不包含,就调用aspect_swizzleForwardInvocation()方法,并把className加入到Set集合里面。

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_modifySwizzledClasses方法里面保证了swizzledClasses这个Set集合是全局唯一的,并且给传入的block加上了线程锁@synchronized,保证了block调用中线程是安全的。

关于调用aspect_swizzleForwardInvocation,将原IMP指向forwardInvocation是下个阶段的事情,我们先把hookClass看完。

// 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; aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass, statedClass); objc_registerClassPair; } object_setClass(self, subclass);

当className没有包含@"_Aspects_"后缀,并且也不是元类,也不是KVO的中间类,即statedClass = = baseClass 的情况,于是,默认的新建一个子类subclass。

到此,我们可以了解到Aspects的设计思想,hook 是在runtime中动态创建子类的基础上实现的。所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,也就是,当你在 remove aspects 的时候,如果发现当前对象的 aspect 都被移除了,那么,你可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,同时也不会影响到其他该类的不同对象)这样对原来替换的类或者对象没有任何影响而且可以在子类基础上新增或者删除aspect。

新建的类的名字,会先加上AspectsSubclassSuffix后缀,即在className后面加上@"_Aspects_",标记成子类。再调用objc_getClass方法,创建这个子类。

/************************************************************************ objc_getClass. Return the id of the named class. If the class does* not exist, call _objc_classLoader and then objc_classHandler, either of * which may create a new class.* Warning: doesn't work if aClassName is the name of a posed-for class's isa!**********************************************************************/Class objc_getClass(const char *aClassName){ if (!aClassName) return Nil; // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES);}

objc_getClass会调用look_up_class方法。

/************************************************************************ look_up_class* Look up a class by name, and realize it.* Locking: acquires runtimeLock**********************************************************************/Class look_up_class(const char *name, bool includeUnconnected __attribute__, bool includeClassHandler __attribute__){ if  return nil; Class result; bool unrealized; { rwlock_reader_t lock(runtimeLock); result = getClass; unrealized = result && !result->isRealized(); } if (unrealized) { rwlock_writer_t lock(runtimeLock); realizeClass; } return result;}

这个方法会去查看有没有实现叫name的class,查看过程中会用到rwlock_reader_t lock(runtimeLock),读写锁,底层是用pthread_rwlock_t实现的。

由于是我们刚刚新建的一个子类名,很有可能是objc_getClass()返回nil。那么我们需要新建这个子类。调用objc_allocateClassPair()方法。

/************************************************************************ objc_allocateClassPair* fixme* Locking: acquires runtimeLock**********************************************************************/Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes){ Class cls, meta; rwlock_writer_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. if (getClass || !verifySuperclass(superclass, true/*rootOK*/)) { return nil; } // Allocate new classes. cls = alloc_class_for_subclass(superclass, extraBytes); meta = alloc_class_for_subclass(superclass, extraBytes); // fixme mangle the name if it looks swift-y? objc_initializeClassPair_internal(superclass, name, cls, meta); return cls;}

调用objc_allocateClassPair会新建一个子类,它的父类是入参superclass。

如果新建的子类subclass = = nil,就会报错,objc_allocateClassPair failed to allocate class。

aspect_swizzleForwardInvocation这是下一阶段的事情,主要作用是替换当前类forwardInvocation方法的实现为__ASPECTS_ARE_BEING_CALLED__,先略过。

接着调用aspect_hookedGetClass 方法。

static void aspect_hookedGetClass(Class class, Class statedClass) { NSCParameterAssert; NSCParameterAssert(statedClass); Method method = class_getInstanceMethod(class, @selector; IMP newIMP = imp_implementationWithBlock(^ { return statedClass; }); class_replaceMethod(class, @selector, newIMP, method_getTypeEncoding;}

aspect_hookedGetClass方法是把class的实例方法替换成返回statedClass,也就是说把调用class时候的isa指向了statedClass了。

 aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass, statedClass);

这两句的意图我们也就明白了。

第一句是把subclass的isa指向了statedClass,第二句是把subclass的元类的isa,也指向了statedClass。

最后调用objc_registerClassPair 注册刚刚新建的子类subclass,再调用object_setClass(self, subclass);把当前self的isa指向子类subclass。

至此,hookClass阶段就完成了,成功的把self hook成了其子类 xxx_Aspects_。

图片 3

从上面我们可以发现,在发消息的时候,如果 selector 有对应的 IMP,则直接执行,如果没有就进行查找,如果最后没有查找到。OC 给我们提供了几个可供补救的机会,依次有 resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation。

/// 为一个指定的类的某个方法执行前/替换/后,添加一段代码块.对这个类的所有对象都会起作用.
///
/// @param block  方法被添加钩子时,Aspectes会拷贝方法的签名信息.
/// 第一个参数将会是 `id<AspectInfo>`,余下的参数是此被调用的方法的参数.
/// 这些参数是可选的,并将被用于传递给block代码块对应位置的参数.
/// 你甚至使用一个没有任何参数或只有一个`id<AspectInfo>`参数的block代码块.
///
/// @注意 不支持给静态方法添加钩子.
/// @return 返回一个唯一值,用于取消此钩子.
  (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// 为一个指定的对象的某个方法执行前/替换/后,添加一段代码块.只作用于当前对象.
 - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; 
/// 撤销一个Aspect 钩子.
/// @return YES 撤销成功, 否则返回 NO. 
id<AspectToken> aspect = ...; 
[aspect remove];

核心代码aspect_prepareClassAndHookSelector

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()函数返回要hook类,针对类和对象的hook返回值不同。

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);

    //判断类是否是_Aspects_前缀开头,对单个实例对象hook,会生成以_Aspects_为前缀中间类
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;

    //表明这里是hook类而不是单个对象
    }else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);

    //[self classs]和object_getClass(self)不相等,表明可能是已经KVO过的实例对象,KVO的实现也是生成一个中间类,改变对象的isa指针指向这个中间类
    }else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    //进入这里表明self是一个实例对象,并没之前没有hook任何方法
    //生成一个以_Aspects_前缀的类名
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    //获取类对象,如果为空,则通过runtime动态生成子类继承
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        //runtime创建子类
        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);
        //更改_Aspects_子类的- (Class)class;方法的返回值,返回的是原来的类(即_Aspects_的父类)
        aspect_hookedGetClass(subclass, statedClass);
        //同上,更改_Aspects_子类的元类为原来的类的元类
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        objc_registerClassPair(subclass);
    }
    //将self的isa指针指向_Aspects_开头的子类
    object_setClass(self, subclass);
    return subclass;
}

从上可以看到,如果是hook类,直接返回这个类;如果是hook对象,会动态生成_Aspects_前缀的中间类并将对象的isa设置为这个中间类(类似KVO),如果对象是已经KVO过的,则直接返回KVO生成的中间类。拿到要hook的类之后,就要将原始方法IMP实现进行替换了,替换是在aspect_swizzleForwardInvocation()函数进行。

static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);

    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

class_replaceMethod()将forwardInvocation的实现替换成__ASPECTS_ARE_BEING_CALLED__()函数的实现,如果替换成功(说明子类重写了该方法)会返回替换之前的IMP,然后为该类添加一个@selector(__aspects_forwardInvocation:)方法。我们知道forwardInvocation方法是runtime消息转发机制中的最后一步转发,Aspects是如何将消息转发到这的呢?继续往下看就知道了。
回到aspect_prepareClassAndHookSelector()函数,接来下通过aspect_isMsgForwardIMP()判断原始selector的IMP是否是_objc_msgForward或者_objc_msgForward_stret,如果不是就将selector的IMP替换成_objc_msgForward或者_objc_msgForward_stret。另外通过runtime添加一个了aspects_为前缀的方法,IMP为原始selector的IMP。对于_objc_msgForward()这个函数,它是直接触发消息转发的,这里就对应了前面的替换@selector(forwardInvocation:)IMP的用意。当我们给hook的selector发送消息时,先执行_objc_msgForward函数,然后触发消息转发来到forwardInvocation:。

为什么这里分为_objc_msgForward或者_objc_msgForward_stret,原因是函数的返回值是存放在寄存器中,寄存器的大小有限,当返回值是某些很大的结构体的时候,寄存器存放不下,就会改成另外一种方式。申请一段内存,把指针保存在寄存器上,返回值往这个指针指向的内存写数据,这时就是用_objc_msgForward_stret方法了。

最后,我们只剩下__ASPECTS_ARE_BEING_CALLED__()函数没有分析了。在前面的操作中有将forwardInvocation方法的IMP替换成这个函数,所以hook之后的消息最终会转发到这个函数里面执行,下面看看这个函数做了哪些事情。

2. ASPECTS_ARE_BEING_CALLED

在上一阶段hookClass的时候,有几处都调用了aspect_swizzleForwardInvocation方法。

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";static void aspect_swizzleForwardInvocation(Class klass) { NSCParameterAssert; // If there is no method, replace will act like class_addMethod. IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), __ASPECTS_ARE_BEING_CALLED__, "v@:@"); if (originalImplementation) { class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); } AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass;}

aspect_swizzleForwardInvocation就是整个Aspects hook方法的开始。

/************************************************************************ class_replaceMethod**********************************************************************/IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types){ if  return nil; return _class_addMethod(cls, name, imp, types, YES);}

调用class_replaceMethod方法,实际底层实现是调用_class_addMethod方法。

static IMP _class_addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace){ old_method *m; IMP result = nil; if  types = ""; mutex_locker_t lock(methodListLock); if ((m = _findMethodInClass(cls, name))) { // already exists // fixme atomic result = method_getImplementationm); if  { method_setImplementationm, imp); } } else { // fixme could be faster old_method_list *mlist = (old_method_list *)calloc(sizeof(old_method_list), 1); mlist->obsolete = fixed_up_method_list; mlist->method_count = 1; mlist->method_list[0].method_name = name; mlist->method_list[0].method_types = strdup; if (!ignoreSelector { mlist->method_list[0].method_imp = imp; } else { mlist->method_list[0].method_imp = &_objc_ignored_method; } _objc_insertMethods(cls, mlist, nil); if (!(cls->info & CLS_CONSTRUCTING)) { flush_caches; } else { // in-construction class has no subclasses flush_cache; } result = nil; } return result;}

从上述源码中,我们可以看到,先_findMethodInClass(cls, name),从cls中查找有没有name的方法。如果有,并且能找到对应的IMP的话,就进行替换method_setImplementationm, imp),把name方法的IMP替换成imp。这种方式_class_addMethod返回的是name方法对应的IMP,实际上就是我们替换完的imp。

如果在cls中没有找到name方法,那么就添加该方法,在mlist -> method_list[0] 的位置插入新的name方法,对应的IMP就是传入的imp。这种方式_class_addMethod返回的是nil。

回到aspect_swizzleForwardInvocation中,

IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), __ASPECTS_ARE_BEING_CALLED__, "v@:@");if (originalImplementation) { class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");}

把forwardInvocation:的IMP替换成__ASPECTS_ARE_BEING_CALLED__ 。如果在klass里面找不到forwardInvocation:方法,就会新添加该方法。

由于子类本身并没有实现 forwardInvocation ,隐藏返回的 originalImplementation 将为空值,所以也不会生成 NSSelectorFromString(AspectsForwardInvocationSelectorName) 。所以还需要_class_addMethod会为我们添加了forwardInvocation:方法的实现

谢谢简书的大神 @zhao0 指点,这个坑在Aspects 1.4.1中已经修复了。

在aspect_swizzleForwardInvocation中,class_replaceMethod返回的是原方法的IMP,originalImplementation不为空的话说明原方法有实现,添加一个新方法__aspects_forwardInvocation:指向了原来的originalImplementation,在__ASPECTS_ARE_BEING_CALLED__那里如果不能处理,判断是否有实现__aspects_forwardInvocation,有的话就转发。

如果originalImplementation返回的不是nil,就说明已经替换成功。替换完方法之后,我们在klass中再加入一个叫“__aspects_forwardInvocation:”的方法,对应的实现也是__ASPECTS_ARE_BEING_CALLED__。

接下来就是整个Aspects的核心实现了:__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 *objectContainer = objc_getAssociatedObject(self, aliasSelector); AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass, aliasSelector); AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; NSLog(@"%@",info.arguments); NSArray *aspectsToRemove = nil; …… ……}

这一段是hook前的准备工作:

  1. 获取原始的selector
  2. 获取带有aspects_xxxx前缀的方法
  3. 替换selector
  4. 获取实例对象的容器objectContainer,这里是之前aspect_add关联过的对象。
  5. 获取获得类对象容器classContainer
  6. 初始化AspectInfo,传入self、invocation参数
 // Before hooks. aspect_invoke(classContainer.beforeAspects, info); aspect_invoke(objectContainer.beforeAspects, info);

调用宏定义执行Aspects切片功能

#define aspect_invoke(aspects, info) for (AspectIdentifier *aspect in aspects) { [aspect invokeWithInfo:info]; if (aspect.options & AspectOptionAutomaticRemoval) {  aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect];  } }

之所以这里用一个宏定义来实现里面的功能,是为了获得一个更加清晰的堆栈信息。

宏定义里面就做了两件事情,一个是执行了[aspect invokeWithInfo:info]方法,一个是把需要remove的Aspects加入等待被移除的数组中。

[aspect invokeWithInfo:info]方法在上篇里面详细分析过了其实现,这个函数的主要目的是把blockSignature初始化blockSignature得到invocation。然后处理参数,如果参数block中的参数大于1个,则把传入的AspectInfo放入blockInvocation中。然后从originalInvocation中取出参数给blockInvocation赋值。最后调用[blockInvocation invokeWithTarget:self.block];这里Target设置为self.block。也就执行了我们hook方法的block。

所以只要调用aspect_invoke(classContainer.Aspects, info);这个核心替换的方法,就能hook我们原有的SEL。对应的,函数第一个参数分别传入的是classContainer.beforeAspects、classContainer.insteadAspects、classContainer.afterAspects就能对应的实现before、instead、after对应时间的Aspects切片的hook。

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

这一段代码是实现Instead hooks的。先判断当前insteadAspects是否有数据,如果没有数据则判断当前继承链是否能响应aspects_xxx方法,如果能,则直接调用aliasSelector。注意:这里的aliasSelector是原方法method

 // After hooks. aspect_invoke(classContainer.afterAspects, info); aspect_invoke(objectContainer.afterAspects, info);

这两行是对应的执行After hooks的。原理如上。

至此,before、instead、after对应时间的Aspects切片的hook如果能被执行的,都执行完毕了。

如果hook没有被正常执行,那么就应该执行原来的方法。

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

invocation.selector先换回原来的originalSelector,如果没有被hook成功,那么AspectsForwardInvocationSelectorName还能再拿到原来的IMP对应的SEL。如果能相应,就调用原来的SEL,否则就报出doesNotRecognizeSelector的错误。

[aspectsToRemove makeObjectsPerformSelector:@selector];

最后调用移除方法,移除hook。

图片 4

Aspects 之所以选择在 forwardInvocation 这里处理是因为,这几个阶段特性都不太一样:

 

__ASPECTS_ARE_BEING_CALLED__

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    //原始selector
    SEL originalSelector = invocation.selector;
    //aspects_前缀的selector
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //替换为aspects_前缀的selector,alisaSelector的IMP是原始selector的IMP,也就是表明[invocation invoke]时调用了原始方法
    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;

    //这里执行我们入参时传递的block,对应的option是AspectPositionBefore,表明block执行时机在原始方法之前
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    //这里对应的option是AspectPositionInstead,表明用传递的block替换原来的方法
    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)));
    }

    //这里对应的option是AspectPositionAfter,表明block的执行时机在原始方法之后
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);


    //如果该方法没有被hook,直接执行原始方法
    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];
        }
    }

    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

主要根据入参时的option来插入block的执行时机,至此Aspects实现思路大致已经解析完毕。最后为了有个更清晰的理解,来个简单的总结。

3. prepareClassAndHookSelector

现在又要回到上篇中提到的aspect_prepareClassAndHookSelector方法中来了。

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert; 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; 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, 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); }}

klass是我们hook完原始的class之后得到的子类,名字是带有_Aspects_后缀的子类。因为它是当前类的子类,所以也可以从它这里获取到原有的selector的IMP。

static BOOL aspect_isMsgForwardIMP { return impl == _objc_msgForward#if !defined(__arm64__) || impl == _objc_msgForward_stret#endif ;}

这里是判断当前IMP是不是_objc_msgForward或者_objc_msgForward_stret,即判断当前IMP是不是消息转发。

如果不是消息转发,就先获取当前原始的selector对应的IMP的方法编码typeEncoding。

如果子类里面不能响应aspects_xxxx,就为klass添加aspects_xxxx方法,方法的实现为原生方法的实现。

Aspects整个hook的入口就是这句话:

class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);

由于我们将slector指向_objc_msgForward 和_objc_msgForward_stret,可想而知,当selector被执行的时候,也会触发消息转发从而进入forwardInvocation,而我们又对forwardInvacation进行了swizzling,因此,最终转入我们自己的处理逻辑代码中。

图片 5

resolvedInstanceMethod 适合给类/对象动态添加一个相应的实现forwardingTargetForSelector 适合将消息转发给其他对象处理
forwardInvocation 是里面最灵活,最能符合需求的

AspectOptions : 

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.
}; // 定义切片时机

 

定义错误信息:

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.
};
/**
 修饰常指针
 const int *A; //const修饰指向的对象,A可变,A指向的对象不可变
 int const *A; //const修饰指向的对象,A可变,A指向的对象不可变
 int *const A; //const修饰指针A, A不可变,A指向的对象可变
 const int *const A;//指针A和A指向的对象都不可变

 */
extern NSString *const AspectErrorDomain;

 

在.m 中首先是 定义了一个参与位与运算的枚举,还定义了一个block

// Block internals.
typedef NS_OPTIONS(int, AspectBlockFlags) {
    AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
    AspectBlockFlagsHasSignature          = (1 << 30)
};
typedef struct _AspectBlock { //参考系统的block 定义了一个自己block,关于block学习 https://maniacdev.com/2013/11/tutorial-an-in-depth-guide-to-objective-c-block-debugging
    __unused Class isa; // __unused  如果变量没有使用就不会参与编译,也不会报warning
    AspectBlockFlags flags;
    __unused int reserved;
    void (__unused *invoke)(struct _AspectBlock *block, ...);
    struct {
        unsigned long int reserved;
        unsigned long int size;
        // requires AspectBlockFlagsHasCopyDisposeHelpers
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        // requires AspectBlockFlagsHasSignature
        const char *signature;
        const char *layout;
    } *descriptor;
    // imported variables
} *AspectBlockRef;

 

 

 

 

然后是几个类定义

AspectInfo,主要是 NSInvocation 信息。将NSInvocation包装一层,比如参数信息等。便于直接使用。

AspectIdentifier,一个Aspect的具体内容。主要包含了单个的 aspect 的具体信息,包括执行时机,要执行 block 所需要用到的具体信息:包括方法签名、参数等等。其实就是将我们传入的bloc,包装成AspectIdentifier,便于后续使用。通过我们替换的block实例化。也就是将我们传入的block,包装成了AspectIdentifier

AspectsContainer,一个对象或者类的所有的 Aspects 整体情况,注意这里数组是通过atomic修饰的。

AspectTracker,用于跟踪所改变的类,打上标记,用于替换类方法,防止重复替换类方法。

 

接下来定义了一个参与(切片时机)位与运算的宏 

#define AspectPositionFilter 0x07 // 二进制就是111

 

将具体的`AspectIdentifier `添加到容器中 

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    /**
     NSCParameterAssert 针对C函数的参数断言 详情参考https://www.jianshu.com/p/6e444981ab45
     NSAssert 针对OC方法的条件断言
     NSCAssert  针对C函数的条件断言  NSCAssert(a == 2, @"a must equal to 2"); //第一个参数是条件,如果第一个参数不满足条件,就会记录并打印后面的字符串
     NSParameterAssert  针对OC方法的参数断言 NSParameterAssert(str); //只需要一个参数,如果参数存在程序继续运行,如果参数为空,则程序停止打印日志
     NSCparameterAssert 针对C函数的参数断言
     */
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);

    __block AspectIdentifier *identifier = nil; //关于__block学习了 禅与Objective-C编程 https://github.com/oa414/objc-zen-book-cn/
    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;
}

//自旋锁,如果访问这个锁的线程不是同一优先级的话,可能会造成死锁。具体原因请看 不再安全的 OSSpinLock。

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

 

合成selector的别名 加上@“aspects__”

static SEL aspect_aliasForSelector(SEL selector) {

    NSCParameterAssert(selector);



return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);

}

 

具体的block内存结构定义、flags和block 中定义的枚举掩码 参考

将block 签名转换为方法签名

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    //// 将block转换为自定义的block形式
    AspectBlockRef layout = (__bridge void *)block;
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {// 比对layout的第8字节到11字节的第三十位 是不是1(1就是有签名)
        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); //desc 地址加上16字节
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {//比对layout的第8字节到11字节的第25位 是不是1(1就是有COPY_DISPOSE)
        desc  = 2 * sizeof(void *); //desc 再加 8 字节,这时候的地址才是真正signature的地址
    }
    if (!desc) {
        NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
        AspectError(AspectErrorMissingBlockSignature, description);
        return nil;
    }
    // 转化成NSMethodSignature 对象输出签名
    const char *signature = (*(const char **)desc);
    //根据类型编码返回真正方法签名
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

比较方法和block 的签名

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

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

手动调用消息转发

static BOOL aspect_isMsgForwardIMP(IMP impl) {
    return impl == _objc_msgForward
#if !defined(__arm64__)
    || impl == (IMP)_objc_msgForward_stret
#endif
    ;
}

static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
    IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)  // 在 arm64 的架构上  是va_arg 的结构体 改变了, 所以针对非arm64 的结构进行了处理
    // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
    // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
    // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
    // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
    Method method = class_getInstanceMethod(self.class, selector);
    const char *encoding = method_getTypeEncoding(method);
    BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;// 判断方法的类型的编码第一位是不是 _C_STRUCT_B
    if (methodReturnsStructValue) {
        @try {
            NSUInteger valueSize = 0;
            NSGetSizeAndAlignment(encoding, &valueSize, NULL);

            if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) { // i386 架构  http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
                methodReturnsStructValue = NO;
            }
        } @catch (__unused NSException *e) {}
    }
    if (methodReturnsStructValue) {
        msgForwardIMP = (IMP)_objc_msgForward_stret;
    }
#endif
    return msgForwardIMP;
}

以下是核心类的实现,原作者的注释已经很清晰了

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

// Will undo the runtime changes made.
static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);

    Class klass = object_getClass(self);
    BOOL isMetaClass = class_isMetaClass(klass);
    if (isMetaClass) {
        klass = (Class)self;
    }

    // Check if the method is marked as forwarded and undo that.
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    if (aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Restore the original method implementation.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
        IMP originalIMP = method_getImplementation(originalMethod);
        NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);

        class_replaceMethod(klass, selector, originalIMP, typeEncoding);
        AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }

    // Deregister global tracked selector
    aspect_deregisterTrackedSelector(self, selector);

    // Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
    AspectsContainer *container = aspect_getContainerForObject(self, selector);
    if (!container.hasAspects) {
        // Destroy the container
        aspect_destroyContainerForObject(self, selector);

        // Figure out how the class was modified to undo the changes.
        NSString *className = NSStringFromClass(klass);
        if ([className hasSuffix:AspectsSubclassSuffix]) {
            Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
            NSCAssert(originalClass != nil, @"Original class must exist");
            object_setClass(self, originalClass);
            AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));

            // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
            // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
            //objc_disposeClassPair(object.class);
        }else {
            // Class is most likely swizzled in place. Undo that.
            if (isMetaClass) {
                aspect_undoSwizzleClassInPlace((Class)self);
            }else if (self.class != klass) {
                aspect_undoSwizzleClassInPlace(klass);
            }
        }
    }
}

HOOK Class  主要是 swizzling 类/对象的 forwardInvocation 函数

aspects 的真正的处理逻辑都是在 forwradInvocation 函数里面进行的。对于对象实例而言,源代码中并没有直接 swizzling 对象的 forwardInvocation 方法,而是动态生成一个当前对象的子类,并将当前对象与子类关联,然后替换子类的 forwardInvocation 方法(这里具体方法就是调用了 object_setClass(self, subclass) ,将当前对象 isa 指针指向了 subclass ,同时修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回当前对象的 class。,这个地方特别绕,它的原理有点类似 kvo 的实现,它想要实现的效果就是,将当前对象变成一个 subclass 的实例,同时对于外部使用者而言,又能把它继续当成原对象在使用,而且所有的 swizzling 操作都发生在子类,这样做的好处是你不需要去更改对象本身的类,也就是,当你在 remove aspects 的时候,如果发现当前对象的 aspect 都被移除了,那么,你可以将 isa 指针重新指回对象本身的类,从而消除了该对象的 swizzling ,同时也不会影响到其他该类的不同对象)。对于每一个对象而言,这样的动态对象只会生成一次,这里 aspect_swizzlingForwardInvocation 将使得 forwardInvocation 方法指向 aspects 自己的实现逻辑。

 

 当前对象的子类-----isa指向----> 当前对象----isa指向---->当前类

当前对象----isa指向---->当前对象的子类----isa指向---->当前类

当remove aspects后,当前对象的isa指向当前类

 

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    /**
     [self class] 返回self的类对象
     object_getClass(self) 返回isa指向的类对象,
     */
    Class statedClass = self.class;
    Class baseClass = object_getClass(self); // isa指向的类,那如果是被KVO的属性 怎么办?
    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed   判断是否已经是添加过_Aspects_而创建的子类了,是就直接返回
    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. 创建 _Aspects_ 后缀动态子类
    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);//将当前self设置为子类,这里其实只是更改了self的isa指针而已

return subclass;
}

    

ForwardInvocation: IMP指向__ASPECTS_ARE_BEING_CALLED__  ,再创建 __aspects_forwardInvocation:指向原来的IMP

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");//type Encoding https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

 替换子类class 方法的IMP,

static void aspect_hookedGetClass(Class class, Class statedClass) {
    NSCParameterAssert(class);
    NSCParameterAssert(statedClass);
    Method method = class_getInstanceMethod(class, @selector(class));
    IMP newIMP = imp_implementationWithBlock(^(id self) {
        return statedClass;
    });

    class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
}

经过Swizzle forwardInvocation 的类都保存在这里

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

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}

对参数进行检查是否合格

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
    static NSSet *disallowedSelectorList;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
    });

    // Check against the blacklist.  检查传进来的selector是不是黑名单中的 @"retain", @"release", @"autorelease", @"forwardInvocation:"
    NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }

    // Additional checks. dealloc 只能在调用前hook
    AspectOptions position = options&AspectPositionFilter;// 0x000 0x010 0x100 前三个 和0x111进行&运算,作者想要的结果是 AspectPositionBefore 0x010
    if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }
    //类或者对象 必须响应这个 selector
    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;
    }

    // Search for the current class and the class hierarchy IF we are modifying a class object
    if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];
        //swizzledClassesDict  仅仅是初始化了可变字典,没有存值,怎么就取值了呢
        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;
        }


        do {
            tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {
                if (klass == currentClass) {
                    // Already modified and topmost!  一个类只能hook 一次
                    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)));//只有当这个类为根类的时候才不会继续循环查找。

        // Add the selector as being modified.
        currentClass = klass;
        AspectTracker *subclassTracker = nil;
        do {
            tracker = swizzledClassesDict[currentClass];
            if (!tracker) {
                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)));
    } else {
        return YES;
    }

    return YES;
}

取消已经跟踪的selector

static void aspect_deregisterTrackedSelector(id self, SEL selector) {
    if (!class_isMetaClass(object_getClass(self))) return;

    NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
    NSString *selectorName = NSStringFromSelector(selector);
    Class currentClass = [self class];
    AspectTracker *subclassTracker = nil;
    do {
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        if (subclassTracker) {
            [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName];
        } else {
            [tracker.selectorNames removeObject:selectorName];
        }
        if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) {
            [swizzledClassesDict removeObjectForKey:currentClass];
        }
        subclassTracker = tracker;
    }while ((currentClass = class_getSuperclass(currentClass)));
}

有时间在继续研究。先留着

总结

针对单个实例对象hook(.h文件中的减方法),生成一个动态子类(_Aspects_为后缀),以类似kvo的方式生成一个中间类, 设置hook对象的isa为这个中间类,并且重写中间类的class方法,返回原始类。然后对于要hook的@selector,更改其IMP为_objc
_Msgforward / _objc_Msgforward_stret,某些情况当@selector的返回值是结构体的时候通过_objc_Msgforward_stret来触发消息转发。更改中间类的@selector(forwardInvocation:)的IMP为__ASPECTS_ARE_BEING_CALLED__,给中间类添加一个以aspects__为前缀的方法,IMP为原始@selector的IMP。

4. aspect_remove

aspect_remove整个销毁过程的函数调用栈

- aspect_remove(AspectIdentifier *aspect, NSError **error) └── aspect_cleanupHookedClassAndSelector ├──aspect_deregisterTrackedSelector │ └── aspect_getSwizzledClassesDict ├──aspect_destroyContainerForObject └── aspect_undoSwizzleClassInPlace └── _aspect_modifySwizzledClasses └──aspect_undoSwizzleForwardInvocation

static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) { NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type."); __block BOOL success = NO; aspect_performLocked(^{ id self = aspect.object; // strongify if  { AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector); success = [aspectContainer removeAspect:aspect]; aspect_cleanupHookedClassAndSelector(self, aspect.selector); // destroy token aspect.object = nil; aspect.block = nil; aspect.selector = NULL; }else { NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect]; AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc); } }); return success;}

aspect_remove 是整个 aspect_add的逆过程。aspect_performLocked是保证线程安全。把AspectsContainer都置为空,remove最关键的过程就是aspect_cleanupHookedClassAndSelector(self, aspect.selector);移除之前hook的class和selector。

static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) { NSCParameterAssert; NSCParameterAssert; Class klass = object_getClass; BOOL isMetaClass = class_isMetaClass; if (isMetaClass) { klass = self; } …… ……}

klass是现在的class,如果是元类,就转换成元类。

 // Check if the method is marked as forwarded and undo that. Method targetMethod = class_getInstanceMethod(klass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); if (aspect_isMsgForwardIMP(targetMethodIMP)) { // Restore the original method implementation. const char *typeEncoding = method_getTypeEncoding(targetMethod); SEL aliasSelector = aspect_aliasForSelector; Method originalMethod = class_getInstanceMethod(klass, aliasSelector); IMP originalIMP = method_getImplementation(originalMethod); NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector, NSStringFromSelector(aliasSelector), klass); class_replaceMethod(klass, selector, originalIMP, typeEncoding); AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector); }

先回复MsgForward消息转发函数,获得方法签名,然后把原始转发方法替换回我们hook过的方法。

这里有一个需要注意的问题。

如果当前Student有2个实例,stu1和stu2,并且他们都同时hook了相同的方法study,stu2在执行完aspect_remove,把stu2的study方法还原了。这里会把stu1的study方法也还原了。因为remove方法这个操作是对整个类的所有实例都生效的。

要想每个实例还原各自的方法,不影响其他实例,上述这段代码删除即可。因为在执行 remove 操作的时候,其实和这个对象相关的数据结构都已经被清除了,即使不去恢复 stu2 的study 的执行,在进入 __ASPECTS_ARE_BEING_CALLED__,由于这个没有响应的 aspects ,其实会直接跳到原来的处理逻辑,并不会有其他附加影响。

static void aspect_deregisterTrackedSelector(id self, SEL selector) { if (!class_isMetaClass(object_getClass return; NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); NSString *selectorName = NSStringFromSelector; Class currentClass = [self class]; AspectTracker *subclassTracker = nil; do { AspectTracker *tracker = swizzledClassesDict[currentClass]; if (subclassTracker) { [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName]; } else { [tracker.selectorNames removeObject:selectorName]; } if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) { [swizzledClassesDict removeObjectForKey:currentClass]; } subclassTracker = tracker; }while ((currentClass = class_getSuperclass(currentClass)));}

还要移除AspectTracker里面所有标记的swizzledClassesDict。销毁全部记录的selector。

 AspectsContainer *container = aspect_getContainerForObject(self, selector); if (!container.hasAspects) { // Destroy the container aspect_destroyContainerForObject(self, selector); // Figure out how the class was modified to undo the changes. NSString *className = NSStringFromClass; if ([className hasSuffix:AspectsSubclassSuffix]) { Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]); NSCAssert(originalClass != nil, @"Original class must exist"); object_setClass(self, originalClass); AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass)); // We can only dispose the class pair if we can ensure that no instances exist using our subclass. // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around. //objc_disposeClassPair(object.class); }else { // Class is most likely swizzled in place. Undo that. if (isMetaClass) { aspect_undoSwizzleClassInPlaceself); }else if (self.class != klass) { aspect_undoSwizzleClassInPlace; } } }

最后,我们还需要还原类的AssociatedObject关联对象,以及用到的AspectsContainer容器。

static void aspect_destroyContainerForObject(id<NSObject> self, SEL selector) { NSCParameterAssert; SEL aliasSelector = aspect_aliasForSelector; objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);}

这个方法销毁了AspectsContainer容器,并且把关联对象也置成了nil。

static void aspect_undoSwizzleClassInPlace(Class klass) { NSCParameterAssert; NSString *className = NSStringFromClass; _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { if ([swizzledClasses containsObject:className]) { aspect_undoSwizzleForwardInvocation; [swizzledClasses removeObject:className]; } });}

aspect_undoSwizzleClassInPlace会再调用aspect_undoSwizzleForwardInvocation方法。

static void aspect_undoSwizzleForwardInvocation(Class klass) { NSCParameterAssert; Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName)); Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:)); // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy. IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod); class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@"); AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass;}

最后还原ForwardInvocation的Swizzling,把原来的ForwardInvocation再交换回来。

图片 6

在Aspects这个库了,用到了Method Swizzling有几处,这几处如果处理不好,就会掉“坑”里了。

因此 Aspects 的方案就是,对于待 hook 的 selector,将其指向 objc_msgForward,同时生成一个新的 aliasSelector 指向原来的 IMP,并且 hook 住 forwardInvocation 函数,使他指向自己的实现。按照上面的思路,当被 hook 的 selector 被执行的时候,首先根据 selector 找到了 objc_msgForward ,而这个会触发消息转发,从而进入 forwardInvocation。同时由于 forwardInvocation 的指向也被修改了,因此会转入新的 forwardInvocation 函数,在里面执行需要嵌入的附加代码,完成之后,再转回原来的 IMP。

针对类的所有对象都生效的hook(.h中的加方法),是直接对该类进行处理,不会生成中间类,处理过程同上。

1.aspect_prepareClassAndHookSelector 中可能遇到的“坑”

在aspect_prepareClassAndHookSelector方法中,会把原始的selector hook成_objc_msgForward。但是如果这里的selector就是_objc_msgForward会发生什么呢?

其实这里的坑在作者的代码注释里面已经隐藏的提到了。

在__ASPECTS_ARE_BEING_CALLED__方法中,最后转发消息的那段代码里面有这样一段注释

// If no hooks are installed, call original implementation (usually to throw an exception)

看到这段注释以后,你肯定会思考,为何到了这里就会throw an exception呢?原因是因为找不到NSSelectorFromString(AspectsForwardInvocationSelectorName)对应的IMP。

再往上找,就可以找到原因了。在实现aspect_prepareClassAndHookSelector中,会判断当前的selector是不是_objc_msgForward,如果不是msgForward,接下来什么也不会做。那么aliasSelector是没有对应的实现的。

由于 forwardInvocation 被 aspects 所 hook ,最终会进入到 aspects 的处理逻辑__ASPECTS_ARE_BEING_CALLED__中来,此时如果没有找不到 aliasSelector 的 IMP 实现,因此会在此进行消息转发。而且子类并没有实现 NSSelectorFromString(AspectsForwardInvocationSelectorName),于是转发就会抛出异常。

这里的“坑”就在于,hook的selector如果变成了_objc_msgForward,就会出现异常了,但是一般我们不会去hook _objc_msgForward这个方法,出现这个问题的原因是有其他的Swizzling会去hook这个方法。

比如说JSPatch把传入的 selector 先被 JSPatch hook ,那么,这里我们将不会再处理,也就不会生成 aliasSelector 。就会出现闪退的异常了。

static Class aspect_hookClass(NSObject *self, NSError **error) { ... subclass = objc_allocateClassPair(baseClass, subclassName, 0); ... IMP originalImplementation = class_replaceMethod(subclass, @selector(forwardInvocation:), __ASPECTS_ARE_BEING_CALLED__, "v@:@"); if (originalImplementation) { class_addMethod(subclass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); } else { Method baseTargetMethod = class_getInstanceMethod(baseClass, @selector(forwardInvocation:)); IMP baseTargetMethodIMP = method_getImplementation(baseTargetMethod); if (baseTargetMethodIMP) { class_addMethod(subclass, NSSelectorFromString(AspectsForwardInvocationSelectorName), baseTargetMethodIMP, "v@:@"); } } ...}

这里在这篇文章中给出了一个解决办法:

在对子类的 forwardInvocation方法进行交换而不仅仅是替换,实现逻辑如下,强制生成一个 NSSelectorFromString(AspectsForwardInvocationSelectorName)指向原对象的 forwardInvocation的实现。

注意如果 originalImplementation为空,那么生成的 NSSelectorFromString(AspectsForwardInvocationSelectorName)将指向 baseClass 也就是真正的这个对象的 forwradInvocation ,这个其实也就是 JSPatch hook 的方法。同时为了保证 block 的执行顺序(也就是前面介绍的 before hooks / instead hooks / after hooks ),这里需要将这段代码提前到 after hooks 执行之前进行。这样就解决了 forwardInvocation 在外面已经被 hook 之后的冲突问题。

谢谢简书的大神 @zhao0 指点,在这篇文章详细分析了Aspect和JSPatch各种兼容性问题,经过详细的分析,最后只有4种不兼容的情况。

Aspects hook的过程

消息触发的流程为:原始@selector -> _objc
_Msgforward / _objc_Msgforward_stret -> 触发消息转发@selector(forwardInvocation:) -> __ASPECTS_ARE_BEING_CALLED__(函数内部执行以aspects__为前缀的方法,也就是原始的@selecotr的IMP,根据传入的flag参数,确定插入的代码执行的时机)

2. aspect_hookSelector 可能出现的 “坑”

在Aspects中主要是hook selector,此时如果有多个地方会和Aspects去hook相同方法,那么也会出现doesNotRecognizeSelector的问题。

举个例子,比如说在NSArray中用Aspects 去hook了objectAtIndex的方法,然后在NSMutableArray中Swizzling了objectAtIndex方法。在NSMutableArray中,调用objectAtIndex就有可能出错。

因为还是在于Aspects hook 了selector之后,会把原来的selector变成_objc_msgForward。等到NSMutableArray再去hook这个方法的时候,记录的是IMP就是_objc_msgForward这个了。如果这时objc_msgSend执行原有实现,就会出错了。因为原有实现已经被替换为_objc_msgForward,而真的IMP由于被Aspects先Swizzling掉了,所以找不到。

解决办法还是类似JSPatch的解决办法:

把-forwardInvocation:也进行Swizzling,在自己的-forwardInvocation:方法中进行同样的操作,就是判断传入的NSInvocation的Selector,被Swizzling的方法指向了_objc_msgForward(或_objc_msgForward_stret)如果是自己可以识别的Selector,那么就将Selector变为原有Selector在执行,如果不识别,就直接转发。

最后用一张图总结一下Aspects整体流程:

图片 7

请大家多多指教。

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

           }//if (identifier) 

       }//if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) 

   });
   return identifier;
}

在没有hook之前,ViewController的SEL与IMP关系如下

hook之前.png

hook之后.png

最初的viewWillAppear: 指向了_objc_msgForward
增加了aspects_viewWillAppear:,指向最初的viewWillAppear:的IMP
最初的forwardInvocation:指向了Aspect提供的一个C方法__ASPECTS_ARE_BEING_CALLED__
动态增加了__aspects_forwardInvocation:,
指向最初的forwardInvocation:的IMP

然后,我们再来看看hook后,一个viewWillAppear:的实际调用顺序:

2.object收到selector(viewWillAppear:)的消息
2.找到对应的IMP:_objc_msgForward,执行后触发消息转发机制。
3.object收到forwardInvocation:消息
4.找到对应的IMP:__ASPECTS_ARE_BEING_CALLED__,执行IMP 

//__ASPECTS_ARE_BEING_CALLED__中的逻辑
1.向object对象发送aspects_viewWillAppear:执行最初的viewWillAppear方法的IMP
2.执行插入的block代码
3.如果ViewController无法响应aspects_viewWillAppear,则向object对象发送__aspects_forwardInvocation:来执行最初的forwardInvocation IMP

1、判断能否hook
对Class和MetaClass进行进行合法性检查,判断能否hook,规则如下
1).retain,release,autorelease,forwoardInvocation:不能被hook
2).dealloc只能在方法前hook
3).类的继承关系中,同一个方法只能被hook一次

2.创建AspectsContainer对象,
以"aspects_ " SEL为key,作为关联对象依附到被hook 的对象上

3.创建AspectIdentifier对象,并且添加到AspectsContainer对象里存储起来。这个过程分为两步
生成block的方法签名NSMethodSignature
对比block的方法签名和待hook的方法签名是否兼容(参数个数,按照顺序的类型)
4.根据hook实例对象/类对象/类元对象的方法做不同处理。

A)类方法来hook的时候,分为两步

1.hook类对象的forwoardInvocation:方法,指向一个静态的C方法,
2.并且创建一个aspects_ forwoardInvocation:动态添加到之前的类中

3.hook类对象的viewWillAppear:方法让其指向_objc_msgForward,
4.动态添加aspects_viewWillAppear:指向最初的viewWillAppear:实现

B)Hook实例的方法

Aspects支持只hook一个对象的实例方法

只不过在第4步略有出入,当hook一个对象的实例方法的时候:

1.新建一个子类,_Aspects_ViewController,并且按照上述的方式hook forwoardInvocation:

2.hook _Aspects_ViewController的class方法,让其返回ViewController
hook _Aspects_ViewController_MetaClass,让其返回ViewController

3.调用objc_setClass来修改ViewController的类为_Aspects_ViewController

这样做,就可以通过object_getClass(self)获得类名,然后看看是否有前缀类名来判断是否被hook过了

hook实例方法详解

TestClass *testObj = [[TestClass alloc] init];

    [testObj aspect_hookSelector:NSSelectorFromString(@"testSelector")
                     withOptions:AspectPositionBefore
                      usingBlock:^(id<AspectInfo> aspectInfo) {

                            NSLog(@"Hook testSelector");
                                                                       }
                           error:NULL];
    [testObj testSelector];

hook之前实例的状态.png

hook的过程:
1、通过statedClass = self.class获取self本来的class
(class方法被重写了,用来获取self被hook之前的Class(Target))

2、通过Class baseClass = object_getClass(self)获取self的isa指针实际指向的class
(self在运行时实际的class,表面上看这是一个西瓜(statedClass),实际上这是一个苹果(basedClass))

3、如果baseClass(实际指向的class)已经是被hook过的子类,则返回baseClass。

4.如果baseClass是MetaClass或者被KVO过的Class,则不必再生成subClass,直接在其自身上进行method swizzling。

5.如果不是上述3、4 所述情况,默认情况下需要对被hook的Class进行”isa swizzling”:

1)通过subclass = objc_allocateClassPair(baseClass, subclassName, 0)动态创建一个被hook类(TestClass)的子类(TestClass_Aspects);
2)然后对子类(TestClass_Aspects)的forwardInvocation:进行method swizzling,替换为ASPECTS_ARE_BEING_CALLED,进行消息转发时,实际执行的是ASPECTS_ARE_BEING_CALLED中的方法;
3)重写子类(TestClass_Aspects)的获取类名的方法class,使其返回被hook之前的类的类名(TestClass);
4)将self(TestObj)的isa指针指向子类(TestClass_Aspects)

object_setClass(self, subclass)
//object_setClass将一个对象设置为别的类类型,返回原来的Class

class被hook后的情况:

hook后.png

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

关键词: