iOS阅读理解-Aspects

今天看了Aspects源码,发现用到的知识点很多,所以想写个总结记录下所见所得,不仅帮助自己把零碎的知识点串联起来,以后也方便捡起来复习。

正篇

通过NSObject的Category提供了两个方法,一个实例方法一个类方法,使用方式通俗易懂。

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

- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError *__autoreleasing *)error;
    NSError __autoreleasing *err = nil;
    __unused id<AspectToken> token = [self aspect_hookSelector:@selector(test:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo, NSInteger arg1) {
        NSLog(@"----------");
        NSLog(@"%@ %@", aspectInfo.arguments, aspectInfo.originalInvocation);
    } error:&err];

头文件中的两个Protocol

@protocol AspectToken <NSObject>
//提供用来remove hook
- (BOOL)remove;
@end

///aspect_hookSelector中block的第一个参数
@protocol AspectInfo <NSObject>
/// 当前被hooked的实例
- (id)instance;
/// 原始invocation
- (NSInvocation *)originalInvocation;
/// 所有参数(计算属性)
- (NSArray *)arguments;
@end
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError * __autoreleasing *error) {
    //两个方法都会走到这,一步步解剖下去
    ...
    __block AspectIdentifier *identifier = nil;
    //step1
    aspect_performLocked(^{ 
        //step2
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //AspectsContainer用来保存selector的hook信息
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //step3
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];
                //step4
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

Step1

原文里面使用的是OSSpinLock,但是在iOS10中苹果已经废弃改推荐改用os_unfair_lock,用法其实相差无几,至于原因请参考这里,这里又涉及几个知识点需要了解:线程Qos和优先级反转

如果有一个低优先级的任务正在执行,并且锁定了他操作的资源。
这时候一个同样需要这个资源的高优先级队列中的任务准备执行,那么高优先级的任务就不得不等待低优先级的任务对这个资源解除锁定。
这时候出现了一个中优先级队列中的任务准备执行,他并不需要这个被锁定的资源。
此时高优先级的任务正在等待,可执行的最高优先级的任务就是这个中优先级的任务。
结果可想而知,同样准备执行的情况下,中优先级的任务先于高优先级的任务被执行

//加锁保证线程安全
static void aspect_performLocked(dispatch_block_t block) {
    static os_unfair_lock aspect_lock = OS_UNFAIR_LOCK_INIT;
    os_unfair_lock_lock(&aspect_lock);
    block();
    os_unfair_lock_unlock(&aspect_lock);
}
step2

一系列的合法性判断以及如果hooked的是meta类的话要通过AspectTracker标记元类及父类所需要hook的selector,内部用Set和Map避免被重复hook

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError *__autoreleasing *error) {
    // 过滤黑名单;判断dealloc添加位置;检查selector是否存在
    NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();//全局Map
    ...
    if (class_isMetaClass(object_getClass(self))) {//meta类
        ...
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        AspectTracker *subclassTracker = nil;
        do {
            ...
            //每个层级都需要tracker
        }while ((currentClass = class_getSuperclass(currentClass)));
    } else {
        return YES;
    }
    return YES;
}
step3

AspectIdentifier则是用来追踪当前hooked的selector;在这个方法中获取到aspect_hookSelector的usingBlock的方法签名且检查该block合法性,具体一步步来看。

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError *__autoreleasing*)error {
    //step3-1
    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); 
    //校验blocksignature和被hook的selector参数是否匹配
    //这里selector的参数第一个和第二个永远是id self和SEL _cmd所以直接从第三个参数开始比较
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }
    AspectIdentifier *identifier = nil;
    if (blockSignature) {
        ...
        //create new identifier
    }
    return identifier;
}
  • step3-1
    AspectBlockRef和系统的Block实现非常类似,通过AspectBlockRef捕捉block的方法签名来创建NSMethodSignature
typedef struct _AspectBlock {
    __unused Class isa; // 8byte
    AspectBlockFlags flags; // 4byte
    __unused int reserved; // 4byte
    void (__unused *invoke)(struct _AspectBlock *block, ...); // 8byte
    struct { 
        unsigned long int reserved; // 4byte
        unsigned long int size; // 4byte
        void (*copy)(void *dst, const void *src);
        void (*dispose)(const void *);
        const char *signature;
        const char *layout;
    } *descriptor;
} *AspectBlockRef;

//clang -rewrite-objc得到的cpp文件中关于block的实现
struct __main_block_impl_1 {
  void *isa;//isa指针表示Block可能是个OC对象 
  int Flags;
  int Reserved;
  void *FuncPtr;//指向实现的方法
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_1*, struct __main_block_impl_1*);
  void (*dispose)(struct __main_block_impl_1*);
  //copy和dispose是当有捕捉外部变量的时候才会用得到
};
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;//id转void *需要桥接
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        //throw error
        return nil;
    }
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int);//偏移8byte获取block的signature
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);//如果有copy和dispose需要多偏移16byte
    }
    if (!desc) {
        //throw error
        return nil;
    }
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

通过指针偏移寻找block的方法签名,signature方法签名我这里是$v24@?0@\"<AspectInfo>\"8q16,对应解析v: void 24byte;@?:Block的encode 0:是第一个默认参数block结构体从偏移量0开始;@\"<AspectInfo>\"8: aspectinfo 从偏移量8开始;q: NSInteger 16byte

step4
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError *__autoreleasing *error) {
    Class klass = aspect_hookClass(self, error);//step4-1
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {//step4-2
        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);
        }
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
    }
}
  • step4-1
    aspect_hookClass里面,主要的工作是以当前对象为父类动态创建subclass,改变了self的isa指针指向它并替换subclass的forwardInvocation方法,这有点类似kvo的实现,目的是不影响原来的对象,当销毁subclass的时候把isa指针指向回来的对象即可。
static Class aspect_hookClass(NSObject *self, NSError *__autoreleasing *error) {
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);
    ...
    //这里还判断了如果是meta类或者是被kvo的对象(isa指针不再是原来的Class了),直接hook meta类的forwardInvocation而不是动态创建个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) {
            //throw error
            return nil;
        }
        aspect_swizzleForwardInvocation(subclass);//swizzle forwardinvocation
        aspect_hookedGetClass(subclass, statedClass);
        aspect_hookedGetClass(object_getClass(subclass), statedClass);//object_getClass参数是id类型,它返回的是这个id的isa指针所指向的Class,如果传参是Class,则返回该Class的metaClass
        objc_registerClassPair(subclass);
    }
    object_setClass(self, subclass);//将当前self设置为子类,这里其实只是更改了self的isa指针而已
    return subclass;
}

//hook invocation
static void aspect_swizzleForwardInvocation(Class 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@:@");
    if (originalImplementation) {
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
}

static void aspect_hookedGetClass(Class klass, Class statedClass) {
    //如果statedClass是NSObject则klass是NSObject_Aspects_ 
    Method method = class_getInstanceMethod(klass, @selector(class));
    IMP newIMP = imp_implementationWithBlock(^(id self) {
        return statedClass;
    });
    class_replaceMethod(klass, @selector(class), newIMP, method_getTypeEncoding(method));
}

aspect_swizzleForwardInvocation方法中ASPECTS_ARE_BEING_CALLED是被替换的forwardInvocation
而在aspect_hookedGetClass,把动态创建的subclass的class方法替换成本身对象的class方法;imp_implementationWithBlock根据文档描述是创建一个指向在调用方法时调用block的函数的指针,所以当调用[NSObject_Aspects_ class]时实际上是[NSObject class]

  • step4-2
    这里主要是把动态生成的subclass(XX_Aspects_)中selector替换成forwardInvocation,所有的hooked的方法通过forwardInvocation来进行转发。
...
   if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        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);
        }
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
    }
...

_objc_msgForward是一个函数指针,用来做消息转发的;当向一个对象发送一个未实现的方法时,_objc_msgForward会尝试做消息转发。Aspects在这里判断了需要被hooked的selector是不是消息转发的imp。至于为什么arm64下使用_objc_msgForward_stret可以查看这篇JSPatch实现原理详解_objc_msgForward_stret

runtime源码中的objc-msg-arm64.s中有关于_objc_msgForward的汇编实现;虽然我看不懂,但是看到了里面使用了_objc_msgSend;我猜这就是能唤起消息转发步骤的原因。

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

    //需要#import <objc/message.h>
    //直接调用_objc_msgForward
    IMP imp = _objc_msgForward;
    ((void(*)(id, SEL))imp)(self, @selector(test));

    //发现直接进入消息转发的步骤
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    - (id)forwardingTargetForSelector:(SEL)aSelector
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

至此hook已经完成,总结下主要做了什么
1.动态生成subclass,并将当前对象的isa指向它
2.将subclass的forwardInvocation替换成自己的callback(ASPECTS_ARE_BEING_CALLED)
3.将需要hooked的selector替换成_objc_msgForward,以后进行消息发送的时候直接进入消息转发到subclass的forwardInvocation
4.因为已经替换过了所以执行ASPECTS_ARE_BEING_CALLED

苹果相关文档

BlocksRuntime源码
Runtime源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容