读码笔记-Aspects-iOS源码图解笔记

“读这个干嘛”

1.加深面向对象和面向切面编程结合的实践理解
2.加深OC语言理解

“有啥特色?”

在OC的源码里,面向切面设计比较有代表性的就是KVO的实现原理,
Aspects是面向切面的开源库,一共就俩文件,加起来差不多1000行。

“为啥要用图解,写字不好吗”

图解更具象一点,一排一排的字看起来不累吗……
我个人看起来比较舒服
不舒服的话你可以试试阅读霜老师的文章
iOS 如何实现Aspect Oriented Programming (上)

源码Git传送门:
https://github.com/steipete/Aspects


前菜:

如何理解三个重要的记录类

AspectIdentifier
AspectsContainer
AspectTracker
AspectTracker
AspectsContainer & AspectIdentifier

草,上面我看懂了,那下面这些图怎么看啊?

第一张图表现了从开始调用到将hook信息记录成AspectIdentifier以及AspectTracker的过程
第二、第三张图表现了运用runtime机制进行hook的过程
· 绿色是步骤综述,不想全看完你就看绿色
· 蓝色是步骤,基本上是代表一个方法调用
· 黄色是一个方法里的子步骤

主食:

调用以下方法时,实际上发生了什么?

+ (id)aspect_hookSelector:(SEL)selector
                  withOptions:(AspectOptions)options
                   usingBlock:(id)block
                        error:(NSError **)error {
    return aspect_add((id)self, selector, options, block, error);
}


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

上菜!


aspect.001.png
aspect.002.png
aspect.003.png

“实际调用时发生了什么?”

柜子动了,画不动了……贴源码聊吧

1.在上面第二张图里,我们注意到有两个选择器的实现被替换了,分别是

forwardInvocation: 指向 __ASPECTS_ARE_BEING_CALLED__的实现
以及
__aspects_forwardInvocation: 指向 forwardInvocation:的实现

在实际调用时,因为动态子类并没有实现被调用的方法,所以会直接进入消息转发流程,从- (void)forwardInvocation:方法被拐进下面这段代码的实现里,大致涉及到的点就是从AspectsContainer中提取出AspectIdentifier,然后转为实际执行(具体代码看invokeWithInfo方法)

// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    SEL originalSelector = invocation.selector;
    SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    invocation.selector = aliasSelector;
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
    NSArray *aspectsToRemove = nil;

    // Before hooks.
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    // Instead hooks.
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
        Class klass = object_getClass(invocation.target);
        do {
            if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
                [invocation invoke];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }

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

    // If no hooks are installed, call original implementation (usually to throw an exception)
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }

    // Remove any hooks that are queued for deregistration.
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

优缺点分析:

优点
好用,方便,充满快乐的runtime实践

缺点
· 如果在比这更上层的代码中使用了消息转发,就会产生冲突
· aspect_performLocked中使用的自旋锁OSSpinLockLock并不安全(2016年的信息,不知道现在大清亡了没)

你可能感兴趣的:(读码笔记-Aspects-iOS源码图解笔记)