当我们有需要在所有的控制器的 viewDidLoad
的方法中执行一些代码时,我们可能考虑到在基类控制器 viewDidLoad
方法中添加一些代码。这里就寄希望于所有的开发者都没有忘了继承基类。或者当我们想在指定的某个实例对象的某个方法A中插入一些代码,我们可能考虑到直接进入到这个类,根据某些条件在A方法中添加代码。这其实就对原有类造成一定的破坏。基于这类需求,Aspects
的出现较为完美的解决了这些痛点。
@interface NSObject (Aspects)
+ (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
@end
提供两个接口,其中类方法针对一个类所有的实例,实例方法只会针对单个指定对象。
- selector : 你想hook的方法,这里以
viewDidLoad
为例 - options : 你可以选择在
viewDidLoad
执行前或者之后执行你添加的代码,或者干脆替代掉原有实现 - block : 你的代码,以block的形式执行
- error : hook过程可能的错误信息
流程实现
这里以hook UIViewController
的 viewDidLoad
方法为例,疏通一下整个实现流程
[UIViewController aspect_hookSelector:@selector(viewDidLoad)
withOptions:AspectPositionAfter
usingBlock:^(idinfo) {
NSLog(@"[%@ viewDidLoad]", NSStringFromClass([info.instance class]));
} error:nil];
经过一层跳转来到一个C方法
// self : UIViewController 类对象
// selector : viewDidLoad 对应的selector
// options : AspectPositionAfter
// block : block
// error : nil
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(^{
// A: 先检测能否被hook,规则是
// 1. 某些方法不能被hook [@"retain", @"release", @"autorelease", @"forwardInvocation:"]
// 2. 如果 hook 的是 dealloc 方法,那么 block 必须在 dealloc 执行之前执行。
// 3. 一个类的继承体系中,一个方法只能被hook一次
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
// B: 可以被 hook 的话,则生成一个 AspectIdentifier 并添加到 AspectsContainer 中,
// AspectsContainer 是由关联对象技术存储在 UIViewController 类对象中。
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];
// C: 具体执行 hook 的方法
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
static void aspect_performLocked(dispatch_block_t block) {
static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&aspect_lock);
block();
OSSpinLockUnlock(&aspect_lock);
}
- 添加一个aspect的代码在锁的保护下执行
static void aspect_performLocked(dispatch_block_t block)
。 - A: 先检测这个类的指定方法能否被 hook
- B: 检测通过则生成一个
AspectIdentifier
对象用于记录这次hook的信息 - C: 最后执行 hook 的代码
A: 先检测这个类的指定方法能否被 hook
检测这个方法是否适合被 hook ,如果能被 hood 且被 hook 的是一个类的话,则跟踪这个类整个继承体系。
所谓跟踪整个继承体系是因为:一旦 hook 一个类,会生成一个 AspectTracker0,以 class 为key,AspectTracker0 为value存在静态字典中。
同时,会获取这个类的父类,同样生成一个 AspectTracker1,以 superClass 为key,AspectTracker1 为value存在静态字典中。
并且 将 AspectTracker0 记录在 AspectTracker1 中。再获取父类的父类直到 NSObject
.
跟踪的目的在于一个类的继承体系中,一个方法只能被hook一次, 讨论说可能导致死循环
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];
});
/// 黑名单中的方法不可以被hook
NSString *selectorName = NSStringFromSelector(selector);
if ([disallowedSelectorList containsObject:selectorName]) {
NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
AspectError(AspectErrorSelectorBlacklisted, errorDescription);
return NO;
}
/// 如果 hook 的是 dealloc 方法,那么block的执行时机必须在 dealloc 执行前,
/// 试想如果在 dealloc执行后,对象都被释放了,再执行block可能会崩溃
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;
}
/// 确保被hook的对象能够响应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;
}
/// 如果是类的话一个类的继承体系中,一个方法只能被hook一次
if (class_isMetaClass(object_getClass(self))) {
Class klass = [self class];
/// 静态字典 @ {Class : 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 {
// 如果当前类的父类已经 hook 了这个方法,则不能重复 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)));
/// 比如hook MyViewController(继承自UIViewController)的 `viewDidLoad` 方法,那么首先会生成一个 AspectTracker0 记录被hook的方法名(viewDidLoad)和类(MyViewController),将这个 AspectTracker_My 存在静态的字典中。
/// 然后取到 MyViewController 的父类 UIViewController, 生成一个 AspectTracker1,将 AspectTracker0 存入 AspectTracker1的容器中,以记录 UIViewController的子类被hook的情况
/// 再取到 UIViewController 的父类 。。。同样操作,直至NSObject。
currentClass = klass;
AspectTracker *subclassTracker = nil;
do {
tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
swizzledClassesDict[(id)currentClass] = tracker;
}
if (subclassTracker) {
[tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
} else { // 针对的是当前类进行hook,所以方法被添加到当前类的被hook方法列表中
[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;
}
B: 检测通过则生成一个 AspectIdentifier
对象用于记录这次hook的信息
通过传进来的参数生成一个 AspectIdentifier
对象,并插入到 AspectsContainer
中, AspectsContainer
是根据方法名动态绑定到类或者对象上的
+ (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);
// check signature compatibility, etc.
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;
}
/// 关联对象技术将一个 AspectsContainer 绑定在一个类对象或者实例对象上
/// AspectsContainer 可以包含了一个方法的任一 |前|后|替换| 的AspectIdentifier
/// 取了别名 viewDidLoad -> aspects__viewDidLoad
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
NSCParameterAssert(self);
SEL aliasSelector = aspect_aliasForSelector(selector);
AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
if (!aspectContainer) {
aspectContainer = [AspectsContainer new];
objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
}
return aspectContainer;
}
AspectIdentifier
这个类记录了hook时传进来的参数,并最终以 NSInvocation
的方式执行block
@interface AspectIdentifier : NSObject
/// 被hook的方法
@property (nonatomic, assign) SEL selector;
/// 传进来的block
@property (nonatomic, strong) id block;
/// block的方法签名
@property (nonatomic, strong) NSMethodSignature *blockSignature;
/// 被hook的对象(类或者实例)
@property (nonatomic, weak) id object;
/// block的执行时机
@property (nonatomic, assign) AspectOptions options;
@end
AspectsContainer
一个容器类,存储了一个类或者对象的所有 hook 的信息,一个 hook 信息用 AspectIdentifier
表示
@interface AspectsContainer : NSObject
/// 添加一个 AspectIdentifier, 根据 options 存入相应的容器
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
/// 移除一个 AspectIdentifier,根据 options 从相应的容器内移除
- (BOOL)removeAspect:(id)aspect;
/// 是否有hood信息
- (BOOL)hasAspects;
/// 在原方法之前执行block的hooks
@property (atomic, copy) NSArray *beforeAspects;
/// 代替原方法执行block的hooks
@property (atomic, copy) NSArray *insteadAspects;
/// 在原方法之后执行block的hooks
@property (atomic, copy) NSArray *afterAspects;
@end
C: 最后执行 hook 的代码
- 将一个类的
forwardInvocation:
的实现 替换为__ASPECTS_ARE_BEING_CALLED__
- 生成一个新的方法
aspects__viewDidLoad
保存原有方法viewDidLoad
的实现 - 将指定方法
viewDidLoad
的实现修改为_objc_msgForward
于是viewDidLoad
-> _objc_msgForward
这样调用 viewDidLoad
就会直接执行 _objc_msgForward
, 从而触发消息转发执行 forwardInvocation:
,而 forwardInvocation:
-> __ASPECTS_ARE_BEING_CALLED__
。 所以最终调用 viewDidLoad
会执行 __ASPECTS_ARE_BEING_CALLED__
.
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
// 1.将一个类的 `forwardInvocation:` 的实现 替换为 `__ASPECTS_ARE_BEING_CALLED__
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);
// 2. 生成一个新的方法 `aspects__viewDidLoad` 保存原有方法 `viewDidLoad` 的实现
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.
/// 3. 将指定方法 `viewDidLoad` 的实现修改为 `_objc_msgForward`
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
/// 替换掉类方法的 `forwardInvocation:` 的方法实现,将它的实现更改为 `__ASPECTS_ARE_BEING_CALLED__`这个c方法,并确保一个类只会执行一次
/// 如果是实例对象的话,生成一个子类,并修改当前对象的isa指向子类。
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
// 已经替换过了
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// We swizzle a class object, not a single object.
// 替换掉类的 forwardInvocation:方法实现
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
/// KVOed类,通过 self.class方法获得的class为原类,而通过object_getClass(self),是KVO后被修改isa指向的真实的class
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// 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;
}
/// 将一个类的 `forwardInvocation:`方法实现替换为 __ASPECTS_ARE_BEING_CALLED__,如果这个类本身实现了 forwardInvocation:,则生成一个新方法 __aspects_forwardInvocation 保存原有实现
aspect_swizzleForwardInvocation(subclass);
/// 修改一个类的 class 方法实现,作用是修改了一个类的class使之指向子类,但是使用者通过 [self class] 方法仍然返回原来的类
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
/// 替换掉类方法的 `forwardInvocation:` 的方法实现,将它的实现更改为 `__ASPECTS_ARE_BEING_CALLED__`这个c方法,并确保一个类只会执行一次
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;
}
/// 修改一个类的 class 方法实现,作用是修改了一个类的class使之指向子类,但是使用者通过 [self class] 方法仍然返回原来的类
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));
}
__ASPECTS_ARE_BEING_CALLED__
我们已经知道,当一个类或者对象调用原有方法时,会来到这个C方法。在这个C方法里面会执行block,根据 options
在block前后执行原来的方法。
// This is the swizzled forwardInvocation: method.
/// 一个类被替换的forwardInvocation:的实现。
/// invocation 触发消息转发时生成的 invocation 对象 invocation.selector = @selector(viewDidLoad)
/// 调用 block
/// 回调原有方法
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);
// 因为 viewDidLoad 的实现由 aspects__viewDidLoad 保存了,所以这里为了调用原来的实现,需要修改
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)];
}
执行block
block的本质是结构体
-
__unused Class isa;
: 类型 -
AspectBlockFlags flags
:标志位 -
reserved
:保留字段 -
invoke
: 执行函数 -
descriptor
描述信息
你可以对一个block进行强转后获取执行函数 invoke
的签名信息,
typedef NS_OPTIONS(int, AspectBlockFlags) {
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature = (1 << 30)
};
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的中的方法签名信息
- block 中必然有一个方法可以执行,在flags中有体现
- 通过地址偏移找到这个方法的签名信息
- 生成 NSMethodSignature 签名对象
- 通过 NSMethodSignature 生成 NSInvocation
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
// block 中必然有一个方法可以执行,在flags中有体现
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);
// 生成 NSMethodSignature 签名对象
return [NSMethodSignature signatureWithObjCTypes:signature];
}
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
....
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
aspect_invoke(..., info)
.....
}
将block以 NSInvocation
的形式执行
- (BOOL)invokeWithInfo:(id)info {
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// Be extra paranoid. We already check that on hook registration.
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
// block 如果有参数,则第一个参数为 UIViewController 的实例,第二个参数,为 info,剩下的参数会根据原有方法的参数进行填充
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
void *argBuf = NULL;
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if (!(argBuf = reallocf(argBuf, argSize))) {
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
[originalInvocation getArgument:argBuf atIndex:idx];
[blockInvocation setArgument:argBuf atIndex:idx];
}
// block 如果有参数,这里将第一个参数修改为 self.block。
[blockInvocation invokeWithTarget:self.block];
if (argBuf != NULL) {
free(argBuf);
}
return YES;
}