Aspects 原始碼解析
簡介
Aspects是一個輕量的面向切面程式設計(AOP)的第三方庫,面向切面程式設計簡單來說,就是在原來的業務流程之中的某個位置插入額外的功能,不影響之前的邏輯。舉個栗子來說,比如專案的統計埋點,統計頁面進入的次數,我們一般是hook@selector(viewWillAppear:)方法,然後加入埋點上報,這就相當於我們在viewWillAppear中插入了一個切面來附加額外的功能。
Aspects
先來看下標頭檔案,標頭檔案中只有兩個方法
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
+方法表示hook對類的所有例項物件生效(注意不是hook類方法),-方法表示對類的單個例項物件生效。
進入.m檔案檢視實現,兩個方法都是呼叫了同一個c函式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];
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
aspect_performLocked函式是給block函式的執行進行加鎖處理,aspect_isSelectorAllowedAndTrack()判斷是否允許hook該方法。
aspect_isSelectorAllowedAndTrack
static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
static NSSet *disallowedSelectorList;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
//設定hook黑名單
disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
});
//檢查黑名單,retain,release,autorelease,forwardInvocation方法不允許hook
NSString *selectorName = NSStringFromSelector(selector);
if ([disallowedSelectorList containsObject:selectorName]) {
NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
AspectError(AspectErrorSelectorBlacklisted, errorDescription);
return NO;
}
//針對dealloc方法的hook有額外要求,插入的block程式碼只能選擇在dealloc之前執行
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;
}
//從![self.class instancesRespondToSelector:selector]可以看出是不能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;
}
//判斷是否是元類,也即是判斷self是類還是物件。若self是類(如UIViewController),進入if分支;若self是物件,則直接renturn YES。
if (class_isMetaClass(object_getClass(self))) {
Class klass = [self class];
//全域性字典,Class為key,value是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-while作用與上面的if相同,都是為了保證一個繼承鏈上只能有一次hook,但是兩個的場景不同。上面的if判斷針對的場景是:先hook子類的方法,再hook父類的同一方法,會從上面的if剔除父類的hook。do-while的場景是:先hook父類的方法,再hook子類的方法,會從這一步剔除子類的hook。注意:針對同一個類的方法hook多次是可以的
do {
tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
if (klass == currentClass) {
return YES;//可以hook同一個類的方法多次
}
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)));
currentClass = klass;
AspectTracker *subclassTracker = nil;
//這一步的do-while是儲存selector並且遍歷父類,將所有的類都進行hook標記,保證整個繼承鏈針對一個方法只能hook一次
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];
}
subclassTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
} else {
//這裡是對某個物件的hook,直接return YES
return YES;
}
return YES;
}
額外補充一點,區分[self class]和object_getClass(self)的不同。如果self是物件,則這兩個返回值一樣,即self的對應的類;如果self是類,[self class]返回的還是這個類本身也就是self,而object_getClass(self)返回的是它的元類。
回到aspect_add()函式,繼續往下看。
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);
}
}
AspectsContainer是一個容器類,AspectIdentifier類用來整合hook資訊的,比如hook的selector,傳遞進來的block,hook的物件等,然後將identifier儲存在aspectContainer中,根據我們傳遞的引數options,存放在內部不同的陣列中。這裡重點注意這個方法
+ (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); //生成block的簽名
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;
}
aspect_blockMethodSignature()將我們傳遞進來的block生成方法簽名。
aspect_blockMethodSignature
Aspects仿照OC自己定義了block,如下:
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結構可以通過clang編譯原始檔,具體步驟可以看我之前的文章。可以看到結構體中有個變數const char *signature,這個就是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];
}
上面的程式碼主要就是拿到signature,主要思路是從block中的descriptor地址偏移位元組取到signature。
生成block簽名之後還要檢測簽名的完整性,主要是檢測block的返回值,引數跟hook的方法的返回值和引數保持一致。
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
NSCParameterAssert(blockSignature);
NSCParameterAssert(object);
NSCParameterAssert(selector);
BOOL signaturesMatch = YES;
//對入參的selector生成方法簽名
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
//判斷block和selector的引數,block的引數個數可以小於等於selector的引數個數
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
if (blockSignature.numberOfArguments > 1) {
const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
if (blockType[0] != '@') {
signaturesMatch = NO;
}
}
//從第三個引數開始比對block和selector的引數是否一致
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;
}
oc中的方法呼叫底層都是通過objc_msgSend()方法呼叫,這個方法預設傳遞兩個引數,一個是呼叫的物件object(self),還有一個是@selector,任何一個OC的方法都有這兩個引數,所以block和selector的比較從index = 2的引數開始。比較引數比較的是引數的型別,如int型別就是i
,SEL型別是:
,OC物件是@
,block是@?
,更多的型別可以看蘋果官方文件中關於typeEncoding的介紹。另外關於block的簽名只有一個預設引數,第一個引數(index=0)是block本身,第二個引數Aspects約定是id<AspectInfo>型別的引數(為了和selector的簽名引數保持一致),所以上面判斷了block中第二個引數是否是id型別。
接下來aspect_prepareClassAndHookSelector()方法就是Aspects核心程式碼了。
核心程式碼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));
}
}
aspect_hookClass()函式返回要hook類,針對類和物件的hook返回值不同。
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
//判斷類是否是_Aspects_字首開頭,對單個例項物件hook,會生成以_Aspects_為字首中間類
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
//表明這裡是hook類而不是單個物件
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
//[self classs]和object_getClass(self)不相等,表明可能是已經KVO過的例項物件,KVO的實現也是生成一箇中間類,改變物件的isa指標指向這個中間類
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
//進入這裡表明self是一個例項物件,並沒之前沒有hook任何方法
//生成一個以_Aspects_字首的類名
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
//獲取類物件,如果為空,則通過runtime動態生成子類繼承
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
//runtime建立子類
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);
//更改_Aspects_子類的- (Class)class;方法的返回值,返回的是原來的類(即_Aspects_的父類)
aspect_hookedGetClass(subclass, statedClass);
//同上,更改_Aspects_子類的元類為原來的類的元類
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
//將self的isa指標指向_Aspects_開頭的子類
object_setClass(self, subclass);
return subclass;
}
從上可以看到,如果是hook類,直接返回這個類;如果是hook物件,會動態生成_Aspects_字首的中間類並將物件的isa設定為這個中間類(類似KVO),如果物件是已經KVO過的,則直接返回KVO生成的中間類。拿到要hook的類之後,就要將原始方法IMP實現進行替換了,替換是在aspect_swizzleForwardInvocation()函式進行。
static void aspect_swizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
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));
}
class_replaceMethod()將forwardInvocation的實現替換成__ASPECTS_ARE_BEING_CALLED__()函式的實現,如果替換成功(說明子類重寫了該方法)會返回替換之前的IMP,然後為該類新增一個@selector(__aspects_forwardInvocation:)方法。我們知道forwardInvocation方法是runtime訊息轉發機制中的最後一步轉發,Aspects是如何將訊息轉發到這的呢?繼續往下看就知道了。
回到aspect_prepareClassAndHookSelector()函式,接來下通過aspect_isMsgForwardIMP()判斷原始selector的IMP是否是_objc_msgForward或者_objc_msgForward_stret,如果不是就將selector的IMP替換成_objc_msgForward或者_objc_msgForward_stret。另外通過runtime新增一個了aspects_為字首的方法,IMP為原始selector的IMP。對於_objc_msgForward()這個函式,它是直接觸發訊息轉發的,這裡就對應了前面的替換@selector(forwardInvocation:)IMP的用意。當我們給hook的selector傳送訊息時,先執行_objc_msgForward函式,然後觸發訊息轉發來到forwardInvocation:。
為什麼這裡分為_objc_msgForward或者_objc_msgForward_stret,原因是函式的返回值是存放在暫存器中,暫存器的大小有限,當返回值是某些很大的結構體的時候,暫存器存放不下,就會改成另外一種方式。申請一段記憶體,把指標儲存在暫存器上,返回值往這個指標指向的記憶體寫資料,這時就是用_objc_msgForward_stret方法了。
最後,我們只剩下__ASPECTS_ARE_BEING_CALLED__()函式沒有分析了。在前面的操作中有將forwardInvocation方法的IMP替換成這個函式,所以hook之後的訊息最終會轉發到這個函式裡面執行,下面看看這個函式做了哪些事情。
__ASPECTS_ARE_BEING_CALLED__
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
//原始selector
SEL originalSelector = invocation.selector;
//aspects_字首的selector
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
//替換為aspects_字首的selector,alisaSelector的IMP是原始selector的IMP,也就是表明[invocation invoke]時呼叫了原始方法
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;
//這裡執行我們入參時傳遞的block,對應的option是AspectPositionBefore,表明block執行時機在原始方法之前
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
//這裡對應的option是AspectPositionInstead,表明用傳遞的block替換原來的方法
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)));
}
//這裡對應的option是AspectPositionAfter,表明block的執行時機在原始方法之後
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
//如果該方法沒有被hook,直接執行原始方法
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];
}
}
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
主要根據入參時的option來插入block的執行時機,至此Aspects實現思路大致已經解析完畢。最後為了有個更清晰的理解,來個簡單的總結。
總結
針對單個例項物件hook(.h檔案中的減方法),生成一個動態子類(_Aspects_為字尾),以類似kvo的方式生成一箇中間類, 設定hook物件的isa為這個中間類,並且重寫中間類的class方法,返回原始類。然後對於要hook的@selector,更改其IMP為_objc
_Msgforward / _objc_Msgforward_stret,某些情況當@selector的返回值是結構體的時候通過_objc_Msgforward_stret來觸發訊息轉發。更改中間類的@selector(forwardInvocation:)的IMP為__ASPECTS_ARE_BEING_CALLED__,給中間類新增一個以aspects__為字首的方法,IMP為原始@selector的IMP。
針對類的所有物件都生效的hook(.h中的加方法),是直接對該類進行處理,不會生成中間類,處理過程同上。
訊息觸發的流程為:原始@selector -> _objc
_Msgforward / _objc_Msgforward_stret -> 觸發訊息轉發@selector(forwardInvocation:) -> __ASPECTS_ARE_BEING_CALLED__(函式內部執行以aspects__為字首的方法,也就是原始的@selecotr的IMP,根據傳入的flag引數,確定插入的程式碼執行的時機)
相關文章
- Aspects原始碼分析原始碼
- iOS Aspects原始碼剖析iOS原始碼
- iOS開發 面向切面程式設計之 Aspects 原始碼解析iOS程式設計原始碼
- iOS AOP 框架 - Aspects 原始碼解讀iOS框架原始碼
- 靜下心來讀原始碼之Aspects原始碼
- iOS 開源庫系列 Aspects核心原始碼分析iOS原始碼
- (長文預警)面向切面 Aspects 原始碼閱讀原始碼
- 從 Aspects 原始碼中我學到了什麼?原始碼
- iOS開發中的AOP利器 - Aspects 原始碼分析(二)iOS原始碼
- iOS開發中的AOP利器 - Aspects 原始碼分析(一)iOS原始碼
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- Spark原始碼-SparkContext原始碼解析Spark原始碼Context
- CountDownLatch原始碼解析CountDownLatch原始碼
- LeakCanary原始碼解析原始碼
- vuex原始碼解析Vue原始碼
- ArrayBlockQueue原始碼解析BloC原始碼
- AsyncTask原始碼解析原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- Express原始碼解析Express原始碼
- Observer原始碼解析Server原始碼
- SparseArray 原始碼解析原始碼
- RecyclerView原始碼解析View原始碼
- Promise 原始碼解析Promise原始碼
- Koa原始碼解析原始碼
- RateLimiter原始碼解析MIT原始碼
- redux原始碼解析Redux原始碼
- SDWebImage原始碼解析Web原始碼
- CyclicBarrier原始碼解析原始碼
- Semaphore原始碼解析原始碼
- Exchanger原始碼解析原始碼
- AbstractQueuedSynchronizer原始碼解析原始碼
- OKio原始碼解析原始碼
- Koa 原始碼解析原始碼
- RxPermission原始碼解析原始碼
- MyBatis原始碼解析MyBatis原始碼
- ArrayList原始碼解析原始碼
- LeakCanary 原始碼解析原始碼
- Vue原始碼解析Vue原始碼