AOP的概念说明
Aspect Oriented Programming(AOP)是较为热门的一个话题。AOP,国内大致译作“面向切面编程”。
AOP和OOP的区别
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。 而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
Aspect的源码说明
/**
Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second.
Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe.
*/
@interface NSObject (Aspects)
/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @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;
/// 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;
@end
aspects是基于Objective-c的消息转发机制实现的,所以会有一些性能上的损耗,不建议把aspect用在for循环中。
头文件中是在NSObject基类上提供的两种切面实现方式,一种是对象方法,一种是类方法。
aspect需要的参数:
- selector 方法名;
- options 切面的时机,枚举类型;
- block 切面的block回调;
- error 切面的错误回调;
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default)
AspectPositionInstead = 1, /// Will replace the original implementation.
AspectPositionBefore = 2, /// Called before the original implementation.
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
typedef NS_ENUM(NSUInteger, AspectErrorCode) {
AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted.
AspectErrorDoesNotRespondToSelector, /// Selector could not be found.
AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed.
AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair.
AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called.
AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large.
AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.
};
无论是类方法还是实例方法,最终内部统一调用aspect_add方法。
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(^{
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
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];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
aspect_performLocked 内部是使用OSSpinLock自旋锁,保证线程的安全;
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];
});
// Check against the blacklist.
NSString *selectorName = NSStringFromSelector(selector);
if ([disallowedSelectorList containsObject:selectorName]) {
NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
AspectError(AspectErrorSelectorBlacklisted, errorDescription);
return NO;
}
// Additional checks.
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;
}
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;
}
// 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];
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 {
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)));
// Add the selector as being modified.
currentClass = klass;
AspectTracker *subclassTracker = nil;
do {
tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
if (subclassTracker) {
[tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
} else {
[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;
}
- 首先单例创建集合disallowedSelectorList,不允许aspect的方法黑名单,retain、release、autorelease、forwardInvocation:,会校验aspect的方法名是否在黑名单列表中,如果存在就不允许切面处理,报错是AspectErrorSelectorBlacklisted;
- 其次校验是否是dealloc方法且切面的时机是否是AspectPositionBefore,如果不是则不允许做切面,dealloc是对象的回收调用方法,切面只能是before,报错是AspectErrorSelectorDeallocPosition;
- 如果当前对象不响应该方法且类也不响应该方法,因为可能是对象方法也可能是类方法,所以就会报错,报错为AspectErrorDoesNotRespondToSelector;
- 针对类方法的处理class_isMetaClass(object_getClass(self)),该if条件针对类方法,如果是对象方法的话,切面针对的是独立的对象个体,所以不存在类的继承链上的同步操作,所以如果是对象方法,则直接返回YES;
- 类方法的处理:在字典aspect_getSwizzledClassesDict查找是否包含了当前类的配置信息,如果存在则说明当前类已经实现了切面处理,则报出错误 AspectErrorSelectorAlreadyHookedInClassHierarchy,否则在类的继承链上通过do-while需要不停往父类遍历(currentClass = class_getSuperclass(currentClass)),判断是否[tracker.selectorNames containsObject:selectorName]包含该方法,如果存在同样的报出AspectErrorSelectorAlreadyHookedInClassHierarchy。如果没有存在上述的配置,则进行添加切面配置,通过AspectTracker来管理,也是通过do-while遍历当前类以及父类,更新继承链上的切面配置信息;
配置类说明
// Tracks a single aspect.
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
// Tracks all aspects for an object/class.
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end
AspectIdentifier 单个对象或者类的针对某一个方法的配置信息的管理; AspectsContainer 单个对象或者类的所有配置信息,包括前置、替换、后置等等切面配置,通过关联引用获取aspect_getContainerForObject; AspectTracker 某个类以及类继承链上的track信息,在方法aspect_isSelectorAllowedAndTrack的判断中起着关键性作用;
AspectIdentfier的创建方式如下
+ (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); // TODO: 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;
}
block的方法签名生成如下
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
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);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
Block的数据结构声明如下
// Block internals.
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强制转换成AspectBlockRef类型;
- 通过&运算,判断是否包含方法签名AspectBlockFlagsHasSignature,如果没有,说明该block不合法,报出错误AspectErrorMissingBlockSignature;
- 获取descriptor结构体的地址,然后做地址偏移,偏移量2 * sizeof(unsigned long int),来到AspectBlockFlagsHasCopyDisposeHelpers的判断逻辑;
- 如果存在标识AspectBlockFlagsHasCopyDisposeHelpers,则存在copy和dispose函数;
- 然后再做地址偏移desc += 2 * sizeof(void *),如果desc为空,继续报错AspectErrorMissingBlockSignature;
- 如果没有问题,则拿到block的签名描述,然后生成block对应的方法签名NSMethodSignature;
block的方法签名跟原方法的方法签名的兼容性校验
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
NSCParameterAssert(blockSignature);
NSCParameterAssert(object);
NSCParameterAssert(selector);
BOOL signaturesMatch = YES;
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
if (blockSignature.numberOfArguments > 1) {
const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
if (blockType[0] != '@') {
signaturesMatch = NO;
}
}
// Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
// The block can have less arguments than the method, that's ok.
if (signaturesMatch) {
for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
// Only compare parameter, not the optional type data.
if (!methodType || !blockType || methodType[0] != blockType[0]) {
signaturesMatch = NO; break;
}
}
}
}
if (!signaturesMatch) {
NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
AspectError(AspectErrorIncompatibleBlockSignature, description);
return NO;
}
return YES;
}
- 方法参数的个数校验,block的方法签名的参数个数不能大于原方法的参数个数;
- 当blockSignature的方法参数大于1,其实index = 1 存放的是AspectInfo的数据,type encoding肯定是@ id类型;
- 如果方法参数大于2,开始比较方法的参数以及类型是否匹配
| 类型 | 默认参数1 | 默认参数2 | 参数1 | 参数2 | 参数 ... |
|---|---|---|---|---|---|
| method | self | selector(_cmd) | 方法参数1 | 方法参数2 | ... |
| block | block | AspectInfo数据 | 方法参数1 | 方法参数2 | ... |
参数的type encoding列表如下:
#define _C_ID '@'
#define _C_CLASS '#'
#define _C_SEL ':'
#define _C_CHR 'c'
#define _C_UCHR 'C'
#define _C_SHT 's'
#define _C_USHT 'S'
#define _C_INT 'i'
#define _C_UINT 'I'
#define _C_LNG 'l'
#define _C_ULNG 'L'
#define _C_LNG_LNG 'q'
#define _C_ULNG_LNG 'Q'
#define _C_FLT 'f'
#define _C_DBL 'd'
#define _C_BFLD 'b'
#define _C_BOOL 'B'
#define _C_VOID 'v'
#define _C_UNDEF '?'
#define _C_PTR '^'
#define _C_CHARPTR '*'
#define _C_ATOM '%'
#define _C_ARY_B '['
#define _C_ARY_E ']'
#define _C_UNION_B '('
#define _C_UNION_E ')'
#define _C_STRUCT_B '{'
#define _C_STRUCT_E '}'
#define _C_VECTOR '!'
#define _C_CONST 'r'
接下来是准备hook操作aspect_prepareClassAndHookSelector
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
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);
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.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
- 先获取要hook的class,在aspect_hookClass方法中获取,源码如下
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.
}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);
}
// 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;
}
aspect_swizzleForwardInvocation(subclass);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
self.class object_getClass(self) 二者的区别如下:
在objc的源码中可以看到
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
- 如果当前是实例对象,class是调用object_getClass -> obj->getIsa() 获取的是isa指针,就是类对象,那就是[obj class]和object_getclass(obj)返回的结果相同;
- 如果当前是类对象,class返回的self,就是自己类对象,object_getClass返回的是类对象的isa,指向的是元类对象;
- 如果当前是元类对象,class返回的是自身,object_getClass返回的isa指向的是根元类对象;
- 如果是根类对象Root class,class返回的是自身,object_getClass返回的isa指向的是根元类对象(Root meta class);
- 如果是根元类对象(Root meta class),class返回的是自身,object_getClass返回的isa指向的是根元类对象(Root meta class)也就是自身;
关系图如下:
- 如果类字符串包含AspectsSubclassSuffix的后缀,说明当前类已经被处理过,直接返回当前类就好;
- 如果为元类,说明当前为类对象,就直接hook当前类对象即可,调用aspect_swizzleClassInPlace;
- 如果当前类跟base类不同,说明被KVO处理过,那直接对KVO的中间类调用aspect_swizzleClassInPlace,不需要单独创建子类来处理;
- 接下来就需要动态创建子类了,创建子类来实现aspect,主要目的是不想影响原有类,如果创建失败,返回错误AspectErrorFailedToAllocateClassPair;
- 接下来调用aspect_swizzleForwardInvocation方法,实现forwardInvocation:方法的swizzle;
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.
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
- 替换方法forwardInvocation:的实现,如果原类中没有实现该方法,class_replaceMethod的作用就等同于class_addMethod,替换后方法的实现是__ASPECTS_ARE_BEING_CALLED__方法;
- 如果originalImplementation不为空,说明原类中存在该方法,则添加方法名为AspectsForwardInvocationSelectorName的方法,IMP实现为原有方法的实现;
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));
}
- aspect_hookedGetClass改变新创建的子类的class方法,IMP实现是原有类,先把子类的isa指向了原有类,再把元类的isa也指向了原类;
回到原方法aspect_prepareClassAndHookSelector的讲解中
- 在子类中获取需要切面的方法,拿到Method和IMP,class_getInstanceMethod该方法会在继承链上不断的寻找;
- 方法aspect_isMsgForwardIMP判断是否是forwardIMP,如果不是,方法aspect_aliasForSelector返回带有aspect标志前缀的方法名,然后在子类中添加带前缀的方法,实现是原方法的IMP;
- class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding) 然后将子类的原方法替换成forwardIMP;
操作流程
kclass
selector --> originalSeletorIMP
switch
|
aspects_selector -> originalSeletorIMP
selector -> aspect_getMsgForwardIMP
所以在原有的selector的invoke逻辑中,都会经过forwardIMP,然后进入__ASPECTS_ARE_BEING_CALLED__方法。
获取forwardIMP的实现如下
static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
// As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
// https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
// https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
Method method = class_getInstanceMethod(self.class, selector);
const char *encoding = method_getTypeEncoding(method);
BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
if (methodReturnsStructValue) {
@try {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(encoding, &valueSize, NULL);
if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
methodReturnsStructValue = NO;
}
} @catch (__unused NSException *e) {}
}
if (methodReturnsStructValue) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
#endif
return msgForwardIMP;
}
默认情况下forwardIMP是_objc_msgForward,但是在非arm64架构下,方法的返回参数类型是结构体的话有差异,返回的实现是_objc_msgForward_stret,中间代码是判断是否是方法返回参数类型是否是结构体类型;
接下来就讲解调用方法__ASPECTS_ARE_BEING_CALLED__
// This is a macro so we get a cleaner stack trace.
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
// 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)];
}
#undef aspect_invoke
- ASPECTS_ARE_BEING_CALLED 方法的调用,第一个参数和第二个参数是默认的隐式参数,第三个参数是原方法自带的显式参数;
- 将invocation的selector设置为aliasSelector,然后通过关联引用拿到objectContainer和classContainer;
- 初始化AspectInfo对象;
- 执行before hook aspect_invoke;
- 执行instead hook aspect_invoke;
- 如果objectContainer和classContainer的insteadAspects为空,则基于当前类以及往上遍历继承链,判断继承链上类是否相应方法,[klass instancesRespondToSelector:aliasSelector],如果响应则直接执行[invocation invoke],因为invocation的selector已经调整为aliasSelector,aliasSelector的方法实现已经交换成originalSeletorIMP,所以还是会执行原有的方法实现;
- 最后执行 after hook aspect_invoke;
- respondsToAlias变量是aliasSelector方法是否响应的标识,如果为false,说明当前类以及继承链上不响应aliasSelector方法,说明没有hook成功,那就要进入forward invocation阶段了,此时需要将invocation的selector设置为原有的originalSelector,同时判断当前对象是否响应originalForwardInvocationSEL方法,如果响应则直接调用objc_msgSend方法,originalForwardInvocationSEL对应的IMP就是对象原有的forwardInvocation的实现;
- 否则报错异常,不响应方法,doesNotRecognizeSelector;
- 最后基于aspectsToRemove的数组要调用remove方法,该数组的数据来源于aspect_invoke宏定义中;
接下来讲解下AspectIdentifier的invokeWithInfo的方法,该方法执行aspect的block回调;
- (BOOL)invokeWithInfo:(id<AspectInfo>)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;
}
// The `self` of the block will be the AspectInfo. Optional.
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];
}
[blockInvocation invokeWithTarget:self.block];
if (argBuf != NULL) {
free(argBuf);
}
return YES;
}
- 根据当前AspectInfo获取对应的blockSignature,然后生成对应block的NSInvocation;
- 获取当前AspectInfo的原有的originalInvocation;
- 然后两者做对比,方法参数:blockSignature的numberOfArguments不能大于originalInvocation的;
- 其次在blockInvocation的第一个参数上设置成AspectInfo对象;
- 然后设置第三个以及之后的方法参数,for循环遍历,NSGetSizeAndAlignment根据type encoding大小获取argSize,然后分配内存;
- 原有的invocation获取对应idx下参数 [originalInvocation getArgument:argBuf atIndex:idx];
- blockInvocation重新在刚刚的idx下设置进参数[blockInvocation setArgument:argBuf atIndex:idx];
- blockInvocation invoke执行 target为当前的block,blockInvocation的签名结构参照上面的表格;
- 释放内存argBuf;
最后执行aspectsToRemove的remove操作,对应的方法实现是aspect_remove操作,跟aspect_add方法相对应;
static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");
__block BOOL success = NO;
aspect_performLocked(^{
id self = aspect.object; // strongify
if (self) {
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
success = [aspectContainer removeAspect:aspect];
aspect_cleanupHookedClassAndSelector(self, aspect.selector);
// destroy token
aspect.object = nil;
aspect.block = nil;
aspect.selector = NULL;
}else {
NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
}
});
return success;
}
- 根据AspectIdentifier拿到aspect的对象object,通过关联引用获取到对应的AspectsContainer对象;
- 然后执行AspectsContainer的removeAspect方法,把当前的aspect的关联关系删除;
- 同时执行aspect_cleanupHookedClassAndSelector,把当前的aspect的hook操作撤销;
- 把aspect的数据置空还原;
- 如果aspect为空,说明内存已经回收,报错为AspectErrorRemoveObjectAlreadyDeallocated;
aspect的hook操作撤销实现如下
// Will undo the runtime changes made.
static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
Class klass = object_getClass(self);
BOOL isMetaClass = class_isMetaClass(klass);
if (isMetaClass) {
klass = (Class)self;
}
// Check if the method is marked as forwarded and undo that.
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (aspect_isMsgForwardIMP(targetMethodIMP)) {
// Restore the original method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
IMP originalIMP = method_getImplementation(originalMethod);
NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
class_replaceMethod(klass, selector, originalIMP, typeEncoding);
AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
// Deregister global tracked selector
aspect_deregisterTrackedSelector(self, selector);
// Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
AspectsContainer *container = aspect_getContainerForObject(self, selector);
if (!container.hasAspects) {
// Destroy the container
aspect_destroyContainerForObject(self, selector);
// Figure out how the class was modified to undo the changes.
NSString *className = NSStringFromClass(klass);
if ([className hasSuffix:AspectsSubclassSuffix]) {
Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
NSCAssert(originalClass != nil, @"Original class must exist");
object_setClass(self, originalClass);
AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
// We can only dispose the class pair if we can ensure that no instances exist using our subclass.
// Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
//objc_disposeClassPair(object.class);
}else {
// Class is most likely swizzled in place. Undo that.
if (isMetaClass) {
aspect_undoSwizzleClassInPlace((Class)self);
}else if (self.class != klass) {
aspect_undoSwizzleClassInPlace(klass);
}
}
}
}
- 判断当前self是类对象还是实例对象,如果是类对象,klass也返回当前self;
- 如果当前方法的IMP是aspect_isMsgForwardIMP,先通过aliasSelector -> originalMethod -> originalIMP,然后交换方法实现,结果是seletor -> originalIMP;
- 然后执行aspect_deregisterTrackedSelector,删除类对象的AspectTracker记录,实例对象不用关注;
- 获取当前self关联引用的AspectsContainer,判断当前的AspectsContainer是否有hasAspects,如果没有则删除关联引用aspect_destroyContainerForObject;
- 判断类名是包有后缀AspectsSubclassSuffix,如果是则将类重新设置为以前的class,object_setClass(self, originalClass);
- 官方注销了此代码,objc_disposeClassPair(object.class) 将中间aspect的类注销掉,解释是我们不能释放该类,因为我们不能确保我们的子类没有被使用中;
- 最后注销类关联,aspect_undoSwizzleClassInPlace 跟 aspect_swizzleClassInPlace相对应;
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;
}
static void aspect_undoSwizzleClassInPlace(Class klass) {
NSCParameterAssert(klass);
NSString *className = NSStringFromClass(klass);
_aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
if ([swizzledClasses containsObject:className]) {
aspect_undoSwizzleForwardInvocation(klass);
[swizzledClasses removeObject:className];
}
});
}
一个是add、另一个是remove;一个是aspect_swizzleForwardInvocation,另一个是aspect_undoSwizzleForwardInvocation; 再次将aspect_swizzleForwardInvocation放在这里,方便对比下实现:
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.
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
static void aspect_undoSwizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
// There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
}
- 获取AspectsForwardInvocationSelectorName方法,AspectsForwardInvocationSelectorName的实现是originalImplementation;
- 获取NSObject的forwardInvocation:的Method;
- 如果originalMethod为空的话,originalImplementation为NSObject的实现,否则是原有类klass的forwardInvocation:的IMP;
- 最后将kclass的forwardInvocation:方法的实现替换成originalImplementation。
一切是尘归尘土归土,不会对原有类有影响。
最后顺便提下在block的入参的AspectInfo的属性说明下,业务层可能会用到的属性arguments
关键性代码如下
@implementation NSInvocation (Aspects)
// Thanks to the ReactiveCocoa team for providing a generic solution for this.
- (id)aspect_argumentAtIndex:(NSUInteger)index {
const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
// Skip const type qualifier.
if (argType[0] == _C_CONST) argType++;
#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
__autoreleasing id returnObj;
[self getArgument:&returnObj atIndex:(NSInteger)index];
return returnObj;
} else if (strcmp(argType, @encode(SEL)) == 0) {
SEL selector = 0;
[self getArgument:&selector atIndex:(NSInteger)index];
return NSStringFromSelector(selector);
} else if (strcmp(argType, @encode(Class)) == 0) {
__autoreleasing Class theClass = Nil;
[self getArgument:&theClass atIndex:(NSInteger)index];
return theClass;
// Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
} else if (strcmp(argType, @encode(char)) == 0) {
WRAP_AND_RETURN(char);
} else if (strcmp(argType, @encode(int)) == 0) {
WRAP_AND_RETURN(int);
} else if (strcmp(argType, @encode(short)) == 0) {
WRAP_AND_RETURN(short);
} else if (strcmp(argType, @encode(long)) == 0) {
WRAP_AND_RETURN(long);
} else if (strcmp(argType, @encode(long long)) == 0) {
WRAP_AND_RETURN(long long);
} else if (strcmp(argType, @encode(unsigned char)) == 0) {
WRAP_AND_RETURN(unsigned char);
} else if (strcmp(argType, @encode(unsigned int)) == 0) {
WRAP_AND_RETURN(unsigned int);
} else if (strcmp(argType, @encode(unsigned short)) == 0) {
WRAP_AND_RETURN(unsigned short);
} else if (strcmp(argType, @encode(unsigned long)) == 0) {
WRAP_AND_RETURN(unsigned long);
} else if (strcmp(argType, @encode(unsigned long long)) == 0) {
WRAP_AND_RETURN(unsigned long long);
} else if (strcmp(argType, @encode(float)) == 0) {
WRAP_AND_RETURN(float);
} else if (strcmp(argType, @encode(double)) == 0) {
WRAP_AND_RETURN(double);
} else if (strcmp(argType, @encode(BOOL)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(argType, @encode(bool)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(argType, @encode(char *)) == 0) {
WRAP_AND_RETURN(const char *);
} else if (strcmp(argType, @encode(void (^)(void))) == 0) {
__unsafe_unretained id block = nil;
[self getArgument:&block atIndex:(NSInteger)index];
return [block copy];
} else {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(argType, &valueSize, NULL);
unsigned char valueBytes[valueSize];
[self getArgument:valueBytes atIndex:(NSInteger)index];
return [NSValue valueWithBytes:valueBytes objCType:argType];
}
return nil;
#undef WRAP_AND_RETURN
}
针对参数的type encoding的装包处理,id、Class、SEL等类型直接返回,其他的基本数据类型,char、bool、int、long、double、float、char*等,需要WRAP_AND_RETURN装包处理后返回,block需要copy后返回,剩下的由NSValue,开辟字节数组valueBytes,来持有。
问题: 1. Aspect的实现原理? 2. Aspect的实现性能如何?哪里不好?有没有更好的方案? 3. Aspect会不会跟其他的hook库有冲突(如果同时存在一个项目中)?