“读这个干嘛”
1.加深面向对象和面向切面编程结合的实践理解
2.加深OC语言理解
“有啥特色?”
在OC的源码里,面向切面设计比较有代表性的就是KVO的实现原理,
Aspects是面向切面的开源库,一共就俩文件,加起来差不多1000行。
“为啥要用图解,写字不好吗”
图解更具象一点,一排一排的字看起来不累吗……
我个人看起来比较舒服
不舒服的话你可以试试阅读霜老师的文章
iOS 如何实现Aspect Oriented Programming (上)
源码Git传送门:
https://github.com/steipete/Aspects
前菜:
如何理解三个重要的记录类
AspectIdentifier
AspectsContainer
AspectTracker
草,上面我看懂了,那下面这些图怎么看啊?
第一张图表现了从开始调用到将hook信息记录成AspectIdentifier以及AspectTracker的过程
第二、第三张图表现了运用runtime机制进行hook的过程
· 绿色是步骤综述,不想全看完你就看绿色
· 蓝色是步骤,基本上是代表一个方法调用
· 黄色是一个方法里的子步骤
主食:
调用以下方法时,实际上发生了什么?
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
}
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}
上菜!
“实际调用时发生了什么?”
柜子动了,画不动了……贴源码聊吧
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年的信息,不知道现在大清亡了没)