Aspects
一直在專案中使用,主要使用AOP方式進行tracking,但還沒好好看一看,最近研究了一下原始碼,十分推薦大家閱讀一下,如果只是一味的看Runtime
原始碼,很難真正的掌握還容易忘,配合著看像Aspects
這樣優秀的框架有助於形成知識體系,而且程式碼量也不大,本文只是分析主要的一些原始碼,可以到我的github上看更多原始碼註釋AspectsAnalysis。
NSMethodSignature和NSInvocation
大家可以試試以下程式碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 獲取某個類的例項方法簽名有兩種
NSMethodSignature *signature = [self methodSignatureForSelector:@selector(test:)];
// NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(test:)];
// 獲取某個類的類方法只有一種
// NSMethodSignature *signature = [ViewController methodSignatureForSelector:@selector(test:)];
// 獲取方法簽名對應的invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
// 設定訊息接受者,與[invocation setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等價
[invocation setTarget:self];
// 設定要執行的selector, 與[invocation setArgument:@selector(test:) atIndex:1]
[invocation setSelector:@selector(test:)];
//設定引數
NSString *str = @"hello world";
[invocation setArgument:&str atIndex:2];
//開始執行
[invocation invoke];
}
- (void)test:(NSString*)string{
NSLog(@"test %@",string);
}
複製程式碼
結果是,通過NSMethodSignature
和NSInvocation
也能完成例項物件的方法呼叫
2019-04-20 16:00:57.027828+0800 Test[7439:627386] test hello world
複製程式碼
NSMethodSignature
一個NSMethodSignature
物件記錄著某個方法的返回值型別資訊以及引數型別資訊。它用於轉發訊息接收者無法響應的訊息。
上面程式碼中提供了獲得類方法和例項方法簽名的方式,也可以使用signatureWithObjCTypes
建立方法簽名。
@interface NSMethodSignature
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@end
複製程式碼
NSMethodSignature物件是根據字串建立的,這裡的字串代表了某個方法的返回值型別以及引數型別的字串編碼(Objective-C type encodings)。
個方法簽名包含代表方法返回值的一個或多個字元,後面跟上隱式引數self以及_cmd的字串編碼,然後後面再跟上零個或多個明確的引數的字元編碼。可以通過methodReturnType屬性獲取返回值型別的字元編碼,可以通過methodReturnLength屬性獲取返回值型別的長度。
例如:NSString的例項方法containsString:的方法簽名包含以下引數:
- 返回值:
BOOL
型別, @encode(BOOL) ——c - 方法接收者(self):id型別,@encode(id)——@
- 方法名(_cmd):SEL,@encode(SEL)——:
- 方法引數:NSString型別,@encode(NSString *)——@
NSInvocation
NSInvocation
封裝了方法呼叫物件、方法選擇器、引數、返回值等,可以給物件傳送一個引數大於兩個的訊息,可以直接設定這些元素中的每一個,並在NSInvocation
排程物件時自動設定返回值。
Objective-C方法呼叫過程
在Objective-C
的方法呼叫過程中,如果selector
有對應的IMP
,則直接執行。如果沒有,在丟擲異常之前還有一些彌補機會,依次有resolveInstanceMethod
、forwardingTargetForSelector
、forwardInvocatio
- resolveInstanceMethod (或resolveClassMethod):實現該方法,可以通過
class_addMethod
新增方法,返回YES的話系統在執行時就會重新啟動一次訊息傳送的過程,NO的話會繼續執行下一個方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(runTo:)) {
class_addMethod(self, sel, (IMP)dynamicMethodIMPRunTo, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//動態新增的@selector(runTo:) 對應的實現
static void dynamicMethodIMPRunTo(id self, SEL _cmd,id place){
NSLog(@"dynamicMethodIMPRunTo %@",place);
}
複製程式碼
- forwardingTargetForSelector:實現該方法可以將訊息轉發給其他物件,只要這個方法返回的不是
nil
或self
,也會重啟訊息傳送的過程,把這訊息轉發給其他物件來處理。
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(dynamicSelector) && [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
return self.myObj;
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
複製程式碼
如果上面兩步都無法完成這個SEL
的處理,就會通過forwardInvocation
進行訊息轉發
- methodSignatureForSelector:會去獲取一個方法簽名,如果沒有獲取到的話就回直接挑用
doesNotRecognizeSelector
,如果能獲取的話系統就會建立一個NSlnvocation
傳給forwardInvocation
方法。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{
//判斷selector是否為需要轉發的,如果是則手動生成方法簽名並返回。
if (aSelector == @selector(dynamicSelector)){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super forwardingTargetForSelector:aSelector];
}
複製程式碼
- forwardInvocation:該方法是上一個步傳進來的
NSlnvocation
,然後呼叫NSlnvocation
的invokeWithTarget
方法,轉發到對應的Target
。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//判斷待處理的anInvocation是否為我們要處理的
if (anInvocation.selector == @selector(dynamicSelector)){
}else{
}
}
複製程式碼
- doesNotRecognizeSelector:丟擲
unrecognized selector sent to …
異常
上面描述的是正常的方法呼叫過程,如果想手動出發訊息轉發怎麼辦呢?_objc_msgForward
或者_objc_msgForward_stret
,那他們區別是什麼呢?
IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
if (typeDescription[0] == '{') {
//In some cases that returns struct, we should use the '_stret' API:
//http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
//NSMethodSignature knows the detail but has no API to return, we can only get the info from debugDescription.
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:typeDescription];
if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
}
複製程式碼
JSPatch
通過判斷方法簽名的 debugDescription
是不是包含特定字串is special struct return? YES
,進而決定是否使用 _objc_msgForward_stret
JSPatch
作者解釋
大多數CPU在執行C函式時會把前幾個引數放進暫存器裡,對 obj_msgSend 來說前兩個引數固定是 self / _cmd,它們會放在暫存器上,在最後執行完後返回值也會儲存在暫存器上,取這個暫存器的值就是返回值。普通的返回值(int/pointer)很小,放在暫存器上沒問題,但有些 struct 是很大的,暫存器放不下,所以要用另一種方式,在一開始申請一段記憶體,把指標儲存在暫存器上,返回值往這個指標指向的記憶體寫資料,所以暫存器要騰出一個位置放這個指標,self / _cmd 在暫存器的位置就變了。objc_msgSend 不知道 self / _cmd 的位置變了,所以要用另一個方法 objc_msgSend_stret 代替。原理大概就是這樣。在 NSMethodSignature 的 debugDescription 上打出了是否 special struct,只能通過這字串判斷。所以最終的處理是,在非 arm64 下,是 special struct 就走 _objc_msgForward_stret,否則走 _objc_msgForward。
static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
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;
}
複製程式碼
Aspects
是判斷方法返回值的記憶體大小,來決定是否使用_objc_msgForward_stret
Aspects基本原理
- 將hook的
selector
指向objc_msgForward / _objc_msgForward_stret
。 - 生成
aliasSelector
指向原來的selector
的IMP。 - 將
forwardInvocation
指向自定義的__ASPECTS_ARE_BEING_CALLED__
。 - 生成
__aspects_forwardInvocation
指向原來forwardInvocation
的IMP
Aspects原始碼分析
有前面的鋪墊,再看原始碼就會輕鬆很多,原始碼只會分析一些比較重要的部分。
Aspects一些內部結構和協議
這裡只簡單介紹一下,詳細結構可以檢視原始碼。
- AspectToken:用於登出 Hook。
- AspectInfo:block的第一個引數。
- AspectIdentifier:每進行一個hook,都會生成一個AspectIdentifier物件,包含:方法,block,簽名資訊等。
- AspectsContainer:用於盛放AspectIdentifier物件。一個物件或者類對應一個AspectsContainer物件,有三個陣列,beforeAspects,insteadAspects,afterAspects。
- AspectTracker:每一個class對應一個AspectTracker。 在一個繼承鏈上一個selector只能被hook一次。
_AspectBlock
因為沒法直接拿到block
的簽名資訊,所以建立_AspectBlock
目的是拿到block的簽名資訊,然後就可以使用NSInvocation呼叫這個block。
我們先來看看蘋果原始碼Block_private.h 的Block
記憶體結構,是一個結構體。
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
複製程式碼
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
複製程式碼
再來看看_AspectBlock
,可以很清晰的看到Aspects
仿照系統定義的。
typedef NS_OPTIONS(int, AspectBlockFlags) {
// 捕獲外界變數
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
// 方法有簽名資訊,Block也有簽名資訊
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;
複製程式碼
aspect_blockMethodSignature
方法是用來獲得blocK
的簽名。原理是因為沒法直接拿到block
的簽名資訊,所以將block
強制型別轉換為AspectBlockRef
,根據標誌位和結構體的結構,獲取signature
。
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
// 將block強制轉為AspectBlockRef
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;
}
// descriptor,指標移動
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];
}
複製程式碼
這裡稍微解釋指標移動,我們知道指標是指向一塊記憶體的首地址,desc += 2 * sizeof(unsigned long int);
是因為需要偏移下面兩個unsigned long int)
記憶體大小。
unsigned long int reserved;
unsigned long int size;
複製程式碼
如果捕獲外界變數,這兩個Void
指標是有值得,所以需要偏移兩個Void
指標的記憶體大小desc += 2 * sizeof(void *);
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
複製程式碼
我們看一看block
簽名資訊是什麼樣
[UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info){
NSLog(@"viewDidLoad");
} error:nil];
複製程式碼
返回值是Void
,第一個引數是@?
,表示是Block
,第二個引數@"<AspectInfo>"
,表示遵循了AspectInfo
協議,我們看到block
簽名和方法簽名是不同的,所以需要比較簽名資訊。
aspect_isCompatibleBlockSignature
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
NSCParameterAssert(blockSignature);
NSCParameterAssert(object);
NSCParameterAssert(selector);
BOOL signaturesMatch = YES;
// viewWillAppear: (v @ : c)
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
// block簽名引數一定是小於方法簽名引數
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
if (blockSignature.numberOfArguments > 1) {
const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
// 遵循AspectInfo協議物件
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;
}
複製程式碼
從註釋可以看到簽名資訊引數前兩位是預設的,Argument 0
是self/block
,argument 1
是SEL or id<AspectInfo>
,所以從index = 2開始校驗。
aspect_add
// 這裡的self可以是例項物件 也可以是類物件
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(^{
// 判斷selector是否允許進行hook操作
// 1."retain"、"release"、"autorelease"、"forwardInvocation"這幾個方法是不被允許的。
// 2.如果方法是dealloc,則他的切入點必須是Before。
// 3.判斷當前例項物件和類物件是否能響應方法。
// 4.是否是類物件,如果是則判斷繼承體系中方法是否已經被Hook,而例項則不用。
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
// 通過objc_getAssociatedObject 獲取selector方法的容器
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
// 將block封裝到AspectIdentifier物件中
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
// 通過options將identifier新增到容器對應的beforeAspects,insteadAspects,afterAspects陣列中
[aspectContainer addAspect:identifier withOptions:options];
// HookSelector和HookClass
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
複製程式碼
aspect_prepareClassAndHookSelector
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
// Hook Class,進行swizzleForwardInvocation
// klass類為剛建立的具有_Aspects_字尾的子類
Class klass = aspect_hookClass(self, error);
// 在建立的時候指定類他的父類,所以我們可以獲取到selector這個方法
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
// 如果selector的實現是_objc_msgForward或者_objc_msgForward_stret,就不進行method swizzle 了。
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// 獲得原生方法的型別編碼
const char *typeEncoding = method_getTypeEncoding(targetMethod);
// 給原selector方法名新增字首,並返回。 這個新增字首的selector的實現就是原selector的實現
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
// 沒有使用aliasSelector 儲存selector原來的實現
__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);
}
// 將被hook方法的實現改為forwardInvocation(訊息轉發)
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
複製程式碼
總結一下:
- 將
forwardInvocation
的實現替換為 自定義方法__ASPECTS_ARE_BEING_CALLED__
,並新增__aspects_forwardInvocation
的實現為forwardInvocation
原來的實現。 需要進行訊息轉發的selector
都會執行__ASPECTS_ARE_BEING_CALLED__
- 將
hook
的selector
的實現替換為_objc_msgForward
或者_objc_msgForward_stret
,同時新增aspect_aliasForSelector
的實現為selector
原來的實現。 此時呼叫selector
時就會進行訊息轉發。
aspect_hookClass
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
// 當self是instance(物件)獲取當前Class(類物件),當self是Class(類物件)返回自身
Class statedClass = self.class;
// 獲取isa指標
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// 當hook一個物件的selector時會生成一個子類,子類字首就是AspectsSubclassSuffix。當self對應的類就是生成的子類,直接返回
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// 判斷是否為類物件,如果是,則直接在當前類中進行swizzle
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// 判斷是否為KVO過的物件,因為KVO的物件ISA指標指向一箇中間類,則直接在這個間接勒種進行swizzle
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
/** 當hook一個物件的selector時,實現原理與KVO相似。 1,生成一個子類;2,aspect_swizzleForwardInvocation*/
// 預設情況下,動態建立子類,拼接子類字尾為AspectsSubclassSuffix
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
// 獲取子類isa
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__
aspect_swizzleForwardInvocation(subclass);
// 把生成子類的isa指標指向原生的類
aspect_hookedGetClass(subclass, statedClass);
// 把生成子類的元類的isa指向原生的類
aspect_hookedGetClass(object_getClass(subclass), statedClass);
// 註冊當前生成的子類
objc_registerClassPair(subclass);
}
// 將當前物件的isa指標指向剛生成的類
object_setClass(self, subclass);
return subclass;
}
複製程式碼
總結一下:
- 動態建立子類
- 將子類的
forwardInvocation
的實現替換成__ASPECTS_ARE_BEING_CALLED__
- 把子類的元類的
isa
和子類的元類的isa
指向原生的類 - 註冊子類
- 把
self
物件isa
指標指向子類
圖片來自Aspects關聯&呼叫流程淺析
對某個類的所有例項進行hook
某個類例項進行hook
ASPECTS_ARE_BEING_CALLED
// 訊息經過轉發後都會來到這裡(這裡包括手動訊息轉發和自動訊息轉發)在這裡進行統一的處理: 呼叫block,執行原方法實現
// This is the swizzled forwardInvocation: method.
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
// 拿到originalSelector
SEL originalSelector = invocation.selector;
// originalSelector 加字首得到 aliasSelector,含有字首的方法aspects_
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
// 用 aliasSelector 替換 invocation.selector
invocation.selector = aliasSelector;
// Instance 的容器
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
// Class 的容器
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 {
// 沒有Instead hooks時就執行selector 被hook之前的實現。
Class klass = object_getClass(invocation.target);
// 遍歷 invocation.target及其superClass找到例項可以響應 aliasSelector的invocation invoke
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);
// 呼叫一個沒有實現的selector會觸發 自動訊息轉發,在這種情況下整個繼承鏈中都不會響應aliasSelector也就導致respondsToAlias=false, 開始執行下面的方法
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
// 如果實現了forwardInvocation,執行原來的訊息轉發,否則呼叫doesNotRecognizeSelector,丟擲異常。
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 佇列中的 AspectIdentifier,執行 remove
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
複製程式碼
iOS Aspects原始碼剖析
訊息轉發機制與Aspects原始碼解析
NSMethodSignature
你真的會判斷 _objc_msgForward_stret 嗎
Aspects原始碼解析
從 Aspects 原始碼中我學到了什麼?
Aspects關聯&呼叫流程淺析