Aspects 是 iOS 上的一个轻量级 AOP 库。它利用 method swizzling 技术为已有的类或者实例方法添加额外的代码,它是著名框架 PSPDFKit (an iOS PDF framework that ships with apps like Dropbox or Evernote)的一部分。html
/// Adds a block of code before/instead/after the current `selector` for a specific class.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
复制代码
Aspects 提供了2个 AOP 方法,一个用于类,一个用于实例。在肯定 hook 的 方法以后, Aspects 容许咱们选择 hook 的时机是在方法执行以前,仍是方法执行以后,甚至能够直接替换掉方法的实现。Aspects 的常见使用情景是 log 和 打点统计 等和业务无关的操做。好比 hook ViewController 的 viewWillLayoutSubviews 方法。git
[aspectsController aspect_hookSelector:@selector(viewWillLayoutSubviews) withOptions:0 usingBlock:^{
NSLog(@"Controller is layouting!");
} error:NULL];
复制代码
在阅读 Aspects 源码以前须要一些 Runtime 的相应知识,能够参考我本身的一些博客。github
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
复制代码
接下来的源码解读,主要是分析 Aspects 的 实例方法的执行流程,以及 Aspects 的设计思路。至于 Aspects 的类方法的执行流程和思路也是大同小异,这里就再也不累赘了。app
/// @return A token which allows to later deregister the aspect.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add(self, selector, options, block, error);
}
复制代码
该方法返回一个 AspectToken 对象,这个对象主要是 aspect 的惟一标识符。 该方法调用了 static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) 方法,这个方法用于给一个实例添加 aspect 。框架
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
// ...... 省略代码
__block AspectIdentifier *identifier = nil;
// 给 block 加锁
aspect_performLocked(^{
// 判断 selector 是否能够被 hook
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
// 建立一个 AspectsContainer 对象,用 selector 关联到实例对象
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// 建立一个 AspectIdentifier 对象,
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 把 AspectIdentifier 对象加入 AspectsContainer 对象中
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
复制代码
细看 aspect_isSelectorAllowedAndTrack 方法的内容,看如何判断一个 selector 是否符合 hook 规则ide
// 判断 selector 是否能被 hook
static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
// 不能被 hook 的方法集合
static NSSet *disallowedSelectorList;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
// 这些方法不能被 hook
disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
});
// Check against the blacklist.
// ...... 省略代码
// Additional checks.
AspectOptions position = options&AspectPositionFilter;
// dealloc 方法不容许在执行以后被 hook,由于对象会被销毁
if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
// ...... 省略代码
}
// 被 hook 的方法不存在于类中
if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
// ...... 省略代码
}
// 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];
AspectTracker *tracker = swizzledClassesDict[currentClass];
// 判断子类是否已经 hook 该方法
if ([tracker subclassHasHookedSelectorName:selectorName]) {
// ...... 省略代码
}
do {
// 判断是否已经 hook 了该方法
tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}
} while ((currentClass = class_getSuperclass(currentClass)));
// ...... 省略代码
return YES;
}
复制代码
selector 不容许被 hook 的判断规则函数
接下来看看 static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) 方法实现。ui
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
Class klass = aspect_hookClass(self, error);// 1 swizzling forwardInvocation
// 被 hook 的 selector
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {//2 swizzling method
// 使用一个 aliasSelector 来指向原来 selector 的方法实现
// 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.
// 把 selector 指向 _objc_msgForward 函数
// 用 _objc_msgForward 函数指针代替 selector 的 imp,而后执行这个 imp
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
复制代码
接下来看看 static Class aspect_hookClass(NSObject *self, NSError **error) 的实现spa
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// 是否有 _Aspects_ 后缀
// Already subclassed
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// 动态生成一个当前对象的子类,并将当前对象与子类关联,而后替换子类的 forwardInvocation 方法
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
// 生成 baseClass 对象的子类
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;
}
// 替换子类的 forwardInvocation 方法
aspect_swizzleForwardInvocation(subclass);
// 修改了 subclass 以及其 subclass metaclass 的 class 方法,使他返回当前对象的 class。
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
// 将当前对象 isa 指针指向了 subclass
// 将当前 self 设置为子类,这里其实只是更改了 self 的 isa 指针而已
object_setClass(self, subclass);
return subclass;
}
复制代码
该方法的做用是动态生成一个当前对象的子类,并将当前对象与子类关联,而后替换子类的 forwardInvocation 方法,这样作的好处是不须要去更改对象自己的实例。该方法调用了static void aspect_swizzleForwardInvocation(Class klass) 方法对子类的 forwardInvocation: 方法进行混写;设计
接下来看看 static void aspect_swizzleForwardInvocation(Class klass) 的方法实现,看它如何实现对 forwardInvocation: 方法的混写
//swizzling forwardinvation 方法
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.
// 使用 __ASPECTS_ARE_BEING_CALLED__ 替换子类的 forwardInvocation 方法实现
// 因为子类自己并无实现 forwardInvocation ,
// 因此返回的 originalImplementation 将为空值,因此子类也不会生成 AspectsForwardInvocationSelectorName 这个方法
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
NSLog(@"class_addMethod");
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
复制代码
关键实如今在这句代码,将 forwardInvocation: 的实现换成 __ASPECTS_ARE_BEING_CALLED__实现
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
复制代码
到这里咱们能够知道了,知道 hook 了一个方法,那么最后都会执行 ASPECTS_ARE_BEING_CALLED 这个方法,代码执行到这里基本就到末尾了。咱们看看这个方法实现
// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
// ... 省略代码
// 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 {
// 从新转回原来的 selector 所指向的函数
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)
// 找不到 aliasSelector 的方法实现,也就是没有找到被 hook 的 selector 的原始方法实现,那么进行消息转发
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)];
}
复制代码
找到被 hook 的 originalSelector 的 方法实现
新建一个 aliasSelector 指向原来的 originalSelector 的方法实现
动态建立一个 originalSelector 所在实例的子类,而后 hook 子类的 forwardInvocation: 方法并将方法的实现替换成 ASPECTS_ARE_BEING_CALLED 方法
originalSelector 指向 _objc_msgForward 方法实现
实例的 originalSelector 的方法执行的时候,其实是指向 objc_msgForward ,而 objc_msgForward 的方法实现被替换成 ASPECTS_ARE_BEING_CALLED 的方法实现,也就是说 originalSelector 的方法执行以后,实际上执行的是__ASPECTS_ARE_BEING_CALLED 的方法实现。而 aliasSelector 的做用就是用来保存 originalSelector 的方法实现,当 hook 代码执行完成以后,能够回到 originalSelector 的原始方法实现上继续执行。