A delightful, simple library for aspect oriented programming
關(guān)鍵字:
面向切片編程
、OC動(dòng)態(tài)性
、消息轉(zhuǎn)發(fā)
、類型編碼
、Swizzle
...-
使用場(chǎng)景:
- 1.統(tǒng)一處理邏輯
- 2.在不改變?cè)创a的情況下,插入代碼(如無(wú)侵染更改第三方庫(kù)代碼,干一些壞壞的事情)
Aspects
只有一個(gè)類文件,非常輕量級(jí),在實(shí)現(xiàn)的思路上和JSPatch
差不多。都主要用到OC的消息轉(zhuǎn)發(fā)
,最終都交給ForwardInvocation
實(shí)現(xiàn)。二者很多地方有異曲同工之妙。
基本原理
我們知道 OC
是動(dòng)態(tài)語(yǔ)言,我們執(zhí)行一個(gè)函數(shù)的時(shí)候,其實(shí)是在發(fā)一條消息:[receiver message]
,這個(gè)過(guò)程就是根據(jù) message
生成 selector
,然后根據(jù) selector
尋找指向函數(shù)具體實(shí)現(xiàn)的指針IMP
,然后找到真正的函數(shù)執(zhí)行邏輯。這種處理流程給我們提供了動(dòng)態(tài)性的可能,試想一下,如果在運(yùn)行時(shí),動(dòng)態(tài)的改變了 selector 和 IMP 的對(duì)應(yīng)關(guān)系,那么就能使得原來(lái)的[receiver message]進(jìn)入到新的函數(shù)實(shí)現(xiàn)了。
還是先來(lái)普及一下:
OC
上,每個(gè)類都是這樣一個(gè)結(jié)構(gòu)體:
struct objc_class {
struct objc_class * isa;
const char *name;
….
struct objc_method_list **methodLists; /*方法鏈表*/
};
其中 methodList
方法鏈表里存儲(chǔ)的是 Method
類型:
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;
char *method_types;
IMP method_imp;
};
Method
保存了一個(gè)方法的全部信息,包括 SEL
方法名,type
各參數(shù)和返回值類型,IMP
該方法具體實(shí)現(xiàn)的函數(shù)指針。
通過(guò) Selector
調(diào)用方法時(shí),會(huì)從methodList
鏈表里找到對(duì)應(yīng)Method
進(jìn)行調(diào)用,這個(gè) methodList
上的的元素是可以動(dòng)態(tài)替換的,可以把某個(gè)Selector
對(duì)應(yīng)的函數(shù)指針IMP
替換成新的,也可以拿到已有的某個(gè) Selector
對(duì)應(yīng)的函數(shù)指針IMP
,讓另一個(gè)Selector
跟它對(duì)應(yīng),Runtime
提供了一些接口做這些事。
比如:
static void viewDidLoadIMP (id slf, SEL sel) {
// Custom Code
}
Class cls = NSClassFromString(@"UIViewController");
SEL selector = @selector(viewDidLoad);
Method method = class_getInstanceMethod(cls, selector);
//獲得viewDidLoad方法的函數(shù)指針
IMP imp = method_getImplementation(method)
//獲得viewDidLoad方法的參數(shù)類型
char *typeDescription = (char *)method_getTypeEncoding(method);
//新增一個(gè)ORIGViewDidLoad方法,指向原來(lái)的viewDidLoad實(shí)現(xiàn)
class_addMethod(cls, @selector(ORIGViewDidLoad), imp, typeDescription);
//把viewDidLoad IMP指向自定義新的實(shí)現(xiàn)
class_replaceMethod(cls, selector, viewDidLoadIMP, typeDescription);
這樣就把 UIViewController
的 -viewDidLoad
方法給替換成我們自定義的方法,APP
里調(diào)用 UIViewController
的 viewDidLoad
方法都會(huì)去到上述 viewDidLoadIMP
函數(shù)里,在這個(gè)新的IMP
函數(shù)里調(diào)用新增的方法,就實(shí)現(xiàn)了替換viewDidLoad
方法,同時(shí)為 UIViewController
新增了個(gè)方法 -ORIGViewDidLoad
指向原來(lái)viewDidLoad
的IMP
, 可以通過(guò)這個(gè)方法調(diào)用到原來(lái)的實(shí)現(xiàn)。
.Aspect要的是實(shí)現(xiàn)一個(gè)通用的IMP,任意方法任意參數(shù)都可以通過(guò)這個(gè)IMP中轉(zhuǎn)。上面講的都是針對(duì)某一個(gè)方法的替換,但如果這個(gè)方法有參數(shù),怎樣把參數(shù)值傳給我們新的 IMP
函數(shù)呢?例如 UIViewController
的 -viewDidAppear:
方法,調(diào)用者會(huì)傳一個(gè) Bool
值,我們需要在自己實(shí)現(xiàn)的IMP
(上述的viewDidLoadIMP
)上拿到這個(gè)值,怎樣能拿到?如果只是針對(duì)一個(gè)方法寫IM
P,是可以直接拿到這個(gè)參數(shù)值的。如何達(dá)到通用的效果呢?
如何實(shí)現(xiàn)方法替換
- va_list實(shí)現(xiàn)(一次取出方法的參數(shù))
這段代碼摘至JSPatch
:
static void commonIMP(id slf, ...)
va_list args;
va_start(args, slf);
NSMutableArray *list = [[NSMutableArray alloc] init];
NSMethodSignature *methodSignature = [cls instanceMethodSignatureForSelector:selector];
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
id obj;
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
switch(argumentType[0]) {
case 'i':
obj = @(va_arg(args, int));
break;
case 'B':
obj = @(va_arg(args, BOOL));
break;
case 'f':
case 'd':
obj = @(va_arg(args, double));
break;
…… //其他數(shù)值類型
default: {
obj = va_arg(args, id);
break;
}
}
[list addObject:obj];
}
va_end(args);
[function callWithArguments:list];
}
這樣無(wú)論方法參數(shù)是什么,有多少個(gè),都可以通過(guò)va_list
的一組方法一個(gè)個(gè)取出來(lái),組成 NSArray
。很完美地解決了參數(shù)的問(wèn)題,一直運(yùn)行正常,但是在arm64
下 va_list
的結(jié)構(gòu)改變了,導(dǎo)致無(wú)法上述這樣取參數(shù)。
所以需要找到另一種方法。
-
ForwardInvocation實(shí)現(xiàn)
- 看圖說(shuō)話
從上面我們可以發(fā)現(xiàn),在發(fā)消息的時(shí)候,如果 selector
有對(duì)應(yīng)的 IMP
,則直接執(zhí)行,如果沒(méi)有,oc
給我們提供了幾個(gè)可供補(bǔ)救的機(jī)會(huì),依次有 resolveInstanceMethod
、forwardingTargetForSelector
、forwardInvocation
。
Aspects
之所以選擇在 forwardInvocation
這里處理是因?yàn)?,這幾個(gè)階段特性都不太一樣:
-
resolvedInstanceMethod
: 適合給類/對(duì)象動(dòng)態(tài)添加一個(gè)相應(yīng)的實(shí)現(xiàn), -
forwardingTargetForSelector
:適合將消息轉(zhuǎn)發(fā)給其他對(duì)象處理, -
forwardInvocation
: 是里面最靈活,最能符合需求的。
因此 Aspects
的方案就是,對(duì)于待 hook
的 selector
,將其指向 objc_msgForward / _objc_msgForward_stret
,同時(shí)生成一個(gè)新的 aliasSelector
指向原來(lái)的 IMP,并且 hook
住 forwardInvocation
函數(shù),通過(guò)forwardInvocation
調(diào)用到原來(lái)的IMP。
核心原理:按照上面的思路,當(dāng)被
hook
的selector
被執(zhí)行的時(shí)候,首先根據(jù)selector
找到了objc_msgForward / _objc_msgForward_stret
,而這個(gè)會(huì)觸發(fā)消息轉(zhuǎn)發(fā),從而進(jìn)入forwardInvocation
。同時(shí)由于forwardInvocation
的指向也被修改了,因此會(huì)轉(zhuǎn)入新的forwardInvocation
函數(shù),在里面執(zhí)行需要嵌入的附加代碼,完成之后,再轉(zhuǎn)回原來(lái)的IMP
。
大致流程如下:
-forwardInvocation:
方法的實(shí)現(xiàn)給替換掉了,如果程序里真有用到這個(gè)方法對(duì)消息進(jìn)行轉(zhuǎn)發(fā),原來(lái)的邏輯怎么辦?首先我們?cè)谔鎿Q -forwardInvocation:
方法前會(huì)新建一個(gè)方法 -ORIGforwardInvocation:
,保存原來(lái)的實(shí)現(xiàn)IMP,在新的 -forwardInvocation:
實(shí)現(xiàn)里做了個(gè)判斷,如果轉(zhuǎn)發(fā)的方法是我們想改寫的,就走我們的邏輯,若不是,就調(diào) -ORIGforwardInvocation:
走原來(lái)的流程。
將了這么多可能有些饒。Talk is sheap,show me the code
源碼分析
從頭文件中可以看到使用aspects有兩種使用方式:
- 1.類方法
- 2.實(shí)例方法
/// 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;
兩者的主要原理基本差不多.
先來(lái)看看有哪些定義:
AspectOptions
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.
};
定義切片的調(diào)用時(shí)機(jī)
AspectErrorCode
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.
};
這里定義了在執(zhí)行的時(shí)候的錯(cuò)誤碼,在平時(shí)開(kāi)發(fā)中我們也經(jīng)常使用這種方式,尤其是在定義網(wǎng)絡(luò)請(qǐng)求的時(shí)候。
AspectsContainer
// 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
一個(gè)對(duì)象或者類的所有的 Aspects
整體情況,注意這里數(shù)組是通過(guò)atomic
修飾的。
關(guān)于atomic
需要注意在默認(rèn)情況下,由編譯器所合成的方法會(huì)通過(guò)鎖定機(jī)制確保其原子性(atomicity)
。如果屬性具備nonatomic
特質(zhì),則不需要同步鎖。
注意一共有兩中容器,一個(gè)是對(duì)象的切片,一個(gè)是類的切片。
AspectIdentifier
// 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
一個(gè)Aspect
的具體內(nèi)容。主要包含了單個(gè)的 aspect
的具體信息,包括執(zhí)行時(shí)機(jī),要執(zhí)行 block 所需要用到的具體信息:包括方法簽名、參數(shù)等等。其實(shí)就是將我們傳入的bloc
,包裝成AspectIdentifier,便于后續(xù)使用。通過(guò)我們替換的block實(shí)例化。也就是將我們傳入的block,包裝成了AspectIdentifier
AspectInfo
@interface AspectInfo : NSObject <AspctInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end
主要是 NSInvocation
信息。將NSInvocation
包裝一層,比如參數(shù)信息等。便于直接使用。
AspectTracker
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, weak) AspectTracker *parentEntry;
@end
用于跟蹤所改變的類,打上標(biāo)記,用于替換類方法,防止重復(fù)替換類方法。
流程
讀源碼是一件辛苦的事情。????
__Block的使用
做過(guò)iOS的都知道,__Block
在ARCX
和MRC
環(huán)境修飾對(duì)象下是不同的。具體的內(nèi)容可以看看我的另一篇文章。這里只給出結(jié)論:
在
ARC
環(huán)境下,__Block
會(huì)對(duì)修飾的對(duì)象強(qiáng)引用,在MRC
環(huán)境下對(duì)修飾的對(duì)象不會(huì)強(qiáng)引用。而且__block
修飾局部變量,表示這個(gè)對(duì)象是可以在block
內(nèi)部修改,如果不這樣寫,會(huì)報(bào)Variable is not assignable (missing__block type specifier)
的錯(cuò)誤。
來(lái)看看代碼:
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;
}
注意這里的__block AspectIdentifier *identifier = nil;
為什么要這樣寫呢。上面已經(jīng)說(shuō)過(guò)得很清楚。因?yàn)?code>identifier是局部變量,如果不加__block
修飾,block
里面不能改變identifier
。
自旋鎖(OSSpinLockLock)
Aspect
是線程安全的,那么它是通過(guò)什么方式辦到的呢。如果你對(duì)iOS中的幾個(gè)鎖不清楚,可以看看我的另一篇文章,里面有介紹。
自旋鎖是效率比較高的一種鎖,相比@synchronized
來(lái)說(shuō)效率高得多。但是需要注意,如果訪問(wèn)這個(gè)所的線程不是同一優(yōu)先級(jí)的話,會(huì)有死鎖的潛在風(fēng)險(xiǎn)。具體原因請(qǐng)看YYKit
作者博客。
static void aspect_performLocked(dispatch_block_t block) {
static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&aspect_lock);
// 加鎖執(zhí)行block
block();
// 執(zhí)行完之后釋放鎖
OSSpinLockUnlock(&aspect_lock);
}
通過(guò)這樣的加鎖方式,所以Aspect
作者說(shuō)它是線程安全的。
真正燒腦環(huán)節(jié)
執(zhí)行的入口
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);
}
}
});
上面這句代碼是執(zhí)行Aspect
的入口??词呛?jiǎn)單其實(shí)里面有非常復(fù)雜的地方。
看一看block
做了哪些事情。
- 1.對(duì)出入進(jìn)來(lái)的參數(shù)進(jìn)行檢驗(yàn),保證參數(shù)合法
- 2.創(chuàng)建aspect容器,注意容器是懶加載形式動(dòng)態(tài)添加到
NSObject
分類中作為屬性。 - 3.根據(jù)參數(shù),比如
selector
,option
,創(chuàng)建AspectIdentifier
實(shí)例,上面已經(jīng)說(shuō)過(guò)AspectIdentifier
主要包含了單個(gè)的Aspect
的具體信息,包括執(zhí)行時(shí)機(jī),要執(zhí)行block
所需要用到的具體信息。 - 4.將單個(gè)的
Aspect
的具體信息加到屬性aspectContainer
中 - 5.最為重要的部分進(jìn)行Hook操作,生成子類,類型編碼處理,方法替換等。
下面就對(duì)上面5個(gè)部分分別仔細(xì)的分析。
參數(shù)檢驗(yàn)
嚴(yán)格來(lái)說(shuō),在調(diào)用每一個(gè)方法的時(shí)候都需要對(duì)傳入進(jìn)來(lái)的參數(shù)做一次校驗(yàn)。尤其是在做SDK
的時(shí)候,因?yàn)槟愀静恢劳饷鎮(zhèn)鬟M(jìn)來(lái)的是什么數(shù)據(jù),到底是否為空,數(shù)據(jù)類型是否正確。平時(shí)開(kāi)發(fā)的過(guò)程中,由于我們都知道傳入的參數(shù)大部分來(lái)說(shuō)都是我們復(fù)合預(yù)期的所以就沒(méi)有做什么檢驗(yàn)工作。
回到Aspect中。檢驗(yàn)的內(nèi)容主要有如下幾個(gè):
- 1.Swizzle了不能Swizzle的方法,比如
@retain", @"release", @"autorelease", @"forwardInvocation:"
:如果替換了這樣的方法,我們是不能成功進(jìn)行Swizzle的。 - 2.傳入的執(zhí)行時(shí)機(jī)是否正確,比如對(duì)于
dealloc
方法,`Swizzle之能在之前進(jìn)行調(diào)用。 - 3.對(duì)象或者類是否響應(yīng)傳入的selector
- 4.如果替換的是類方法,則進(jìn)行是否重復(fù)替換的檢查
這里重點(diǎn)捋一捋,類方法的檢驗(yàn)參數(shù),為了檢驗(yàn)類的方法只能修改一次。
if (class_isMetaClass(object_getClass(self))) {
Class klass = [self class];
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
Class currentClass = [self class];
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
// Find the topmost class for the log.
if (tracker.parentEntry) {
AspectTracker *topmostEntry = tracker.parentEntry;
while (topmostEntry.parentEntry) {
topmostEntry = topmostEntry.parentEntry;
}
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}else if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
// Add the selector as being modified.
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
}
- 如何判斷傳入的是類而不是對(duì)象:
class_isMetaClass(object_getClass(self)))
,object_getClass
是獲取當(dāng)前對(duì)象由什么實(shí)例化。
類方法只能替換一次,是在整個(gè)類的繼承樹(shù)上校驗(yàn),而不只是單單的一個(gè)類,從下面代碼可以看出:
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
// Find the topmost class for the log.
if (tracker.parentEntry) {
AspectTracker *topmostEntry = tracker.parentEntry;
while (topmostEntry.parentEntry) {
topmostEntry = topmostEntry.parentEntry;
}
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}else if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
注意這句(currentClass = class_getSuperclass(currentClass)
,當(dāng)且只有當(dāng)這個(gè)類為根類的時(shí)候才不會(huì)繼續(xù)循環(huán)查找。
再來(lái)看看這段:
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
這段的作用就是如果類被修改了,給其父類打上標(biāo)記。然后結(jié)合上面的判斷是否重復(fù)替換。這里為什么要用父類呢。這個(gè)runtime類與父類,根類關(guān)系有關(guān)為了有效的遍歷,需要找到一個(gè)退出的條件,而退出的條件,結(jié)合到runtime就是根類沒(méi)有父類。這就是退出的條件。
如果還沒(méi)有弄懂,回去看看
runtime的基本知識(shí)
創(chuàng)建Aspect容器
AspectsContainer
的作用是為了保存整個(gè)Apects
的情況,包括添加/刪除的aspect
,根據(jù)執(zhí)行的時(shí)機(jī)(before,instead,after)而保存的所有的aspcet
。其中用到了比較常用的動(dòng)態(tài)添加屬性。
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
NSCParameterAssert(self);
// 得到新的SEL(加上了前綴aspects_)并新增為屬性
SEL aliasSelector = aspect_aliasForSelector(selector);
// 得到和aliasSelector相對(duì)應(yīng)的AspectsContainer
AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
if (!aspectContainer) {
aspectContainer = [AspectsContainer new];
objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
}
return aspectContainer;
}
這一步還是比較簡(jiǎn)單。
單個(gè)的 Aspect 的具體信息AspectIdentifier
上面介紹的AspectsContainer
里面裝的具體的東東就是AspectIdentifier
。AspectIdentifier
包含的是具體到每一個(gè)aspect
的具體信息,直接看屬性:
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object; // 具體信息所屬類,用weak
@property (nonatomic, assign) AspectOptions options;
初始化方法將需要的比如:sel
、block
、option
,傳進(jìn)去。`
- (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error`
需要重點(diǎn)注意方法簽名NSMethodSignature
,因?yàn)槲覀冊(cè)谑褂?code>Aspect的時(shí)候是直接通過(guò)block
來(lái)替換方法的,所以需要將我們傳入的block
轉(zhuǎn)換為具體的方法。這也是為什么會(huì)在AspectIdentifier
中多一個(gè)方法簽名的屬性。
在介紹方法簽名之前需要對(duì)block
具體的結(jié)構(gòu)有一定了解。在Aspect
中,我們定義了一個(gè)結(jié)構(gòu)體用來(lái)代替系統(tǒng)的block
??傮w結(jié)構(gòu)其實(shí)和系統(tǒng)的一樣。
// 模仿系統(tǒng)的block結(jié)構(gòu)
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
轉(zhuǎn)為方法簽名的代碼:
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
// 將block轉(zhuǎn)換為自定義的形式
AspectBlockRef layout = (__bridge void *)block;
// 過(guò)濾
if (!(layout->flags & AspectBlockFlagsHasSignature)) {// 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);
// Returns an NSMethodSignature object for the given Objective-C method type string.
// 根據(jù)類型編碼返回真正方法簽名
return [NSMethodSignature signatureWithObjCTypes:signature];
}
block
是作為id
類型進(jìn)行傳遞的,而且是全局類型的block
。如果轉(zhuǎn)換成功,layout
里面都會(huì)有相應(yīng)的值。
這句話const char *signature = (*(const char **)desc);
得到我們傳入block
的類型編碼。通過(guò)類型編碼得到block
所對(duì)應(yīng)的方法簽名。
這里大致給出返回值為空,無(wú)參數(shù)的block
轉(zhuǎn)換之后的方法簽名的結(jié)構(gòu):
<NSMethodSignature: 0x7f9219d064e0>
number of arguments = 1
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
得到了方法簽名,需要對(duì)這個(gè)方法簽名和替換實(shí)際方法進(jìn)行比較。從這里可以感受到做一個(gè)第三方對(duì)參數(shù)的檢測(cè)是非常非常重要的。這也是我們平時(shí)在應(yīng)用開(kāi)發(fā)中所欠缺的。
那怎么和原來(lái)的方法比較呢。比較直接的方法就是,拿到替換方法的方法簽名和我們將block
轉(zhuǎn)換之后的方法簽名對(duì)比。
直接看到代碼:
// 原方法簽名
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
// 參數(shù)不匹配
if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
signaturesMatch = NO;
}else {
if (blockSignature.numberOfArguments > 1) {
// blockSignature參數(shù)沒(méi)有_cmd,
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.
// 對(duì)于block來(lái)說(shuō)
// 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.
// 參數(shù)匹配
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;
}
說(shuō)一說(shuō)為什么要從第二個(gè)參數(shù)開(kāi)始比較呢。首先我們常見(jiàn)的方法都有連個(gè)隱藏的參數(shù),一個(gè)是__cmd
,一個(gè)是self
。而方法簽名的參數(shù)也有兩個(gè)(針對(duì)一個(gè)返回值為空,參數(shù)為空的方法)。第一個(gè)是self
,第二個(gè)是SEL
。源代碼有相關(guān)說(shuō)明。
我直接打印一下:
-
methodSignature:
<NSMethodSignature: 0x7f9219d0cab0>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (B) 'B'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = -7}
memory {offset = 0, size = 1}
argument 0: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (:) ':'
flags {}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}-
blockSignature
<NSMethodSignature: 0x7f9219d064e0>
number of arguments = 1
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (:) ':'
flags {}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
將`block`轉(zhuǎn)換為方法簽名之后就可以給`AspectIdentifier`具體屬性賦值了。
AspectIdentifier *identifier = nil;
if (blockSignature) {
identifier = [AspectIdentifier new];
identifier.selector = selector;
identifier.block = block;
identifier.blockSignature = blockSignature;
identifier.options = options;
identifier.object = object; // weak
}
### 將具體的`AspectIdentifier `添加到容器中
這一步比較簡(jiǎn)單`[aspectContainer addAspect:identifier withOptions:options];`
### 方法替換
方法替換是這個(gè)庫(kù)最為核心的部分,也是最難的部分。里面的思路以及方法很多都值得學(xué)習(xí)。這部分也是自己花了很多時(shí)間去了解。
大致的步驟:
- 1.創(chuàng)建子類,`hook`子類
- 2.處理實(shí)現(xiàn)過(guò)`MsgForwardIMP`的類或者對(duì)象
- 3.將需要替換的`selector`,指向`_objc_msgForward`
來(lái)具體看一看
#### 創(chuàng)建子類
創(chuàng)建子類將`runtime`用到了極致。其實(shí)我們所做的替換完全是在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建子類的時(shí)候?qū)崿F(xiàn)的。這樣對(duì)原來(lái)替換的類或者對(duì)象沒(méi)有任何影響而且可以在子類基礎(chǔ)上新增或者刪除`aspect`。
注意`Class statedClass = self.class;`和`Class baseClass = object_getClass(self);`的區(qū)別,前者獲取類對(duì)象,后者獲取本類是由什么實(shí)例化。
**所有的操作是在類對(duì)象基礎(chǔ)上操作的,而不是一個(gè)對(duì)象**最為重要的就是`aspect_swizzleClassInPlace`方法
static Class aspect_swizzleClassInPlace(Class klass) {
NSCParameterAssert(klass);
NSString *className = NSStringFromClass(klass);
// 1.保證線程安全,2保證數(shù)組單例
_aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
// 不包含這個(gè)類,如果已經(jīng)包含
if (![swizzledClasses containsObject:className]) {
/** 深藏的核心部分 **/
// 將原IMP指向forwardInvocation
aspect_swizzleForwardInvocation(klass);
// 添加進(jìn)來(lái)
[swizzledClasses addObject:className];
}
});
return klass;
}
為了保證線程安全,所以用了`_aspect_modifySwizzledClasses`是單例方法。將傳入的`block`安全執(zhí)行
static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
static NSMutableSet *swizzledClasses;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
swizzledClasses = [NSMutableSet new];
});
@synchronized(swizzledClasses) {
block(swizzledClasses);
}
}
來(lái)看第一個(gè)重頭戲:**aspect_swizzleForwardInvocation**這個(gè)方法的目的就是將本類中的`forwardInvocation`方法替換為我們自定義的`__ASPECTS_ARE_BEING_CALLED__`方法。這樣只要類沒(méi)有找到消息處理著都會(huì)走到自定義的`__ASPECTS_ARE_BEING_CALLED__ `。這樣就可以統(tǒng)一處理了。
這里有個(gè)小小的問(wèn)題,如果`forwardInvocation `已經(jīng)被替換了,那么就需要特殊處理。比如這里判斷的方法是,如果原來(lái)實(shí)現(xiàn)過(guò)`forwardInvocation `。則新增一個(gè)方法`AspectsForwardInvocationSelectorName`,指向`originalImplementation `。這個(gè)時(shí)候已經(jīng)`forwardInvocation`已經(jīng)指向了我們自定義的`AspectsForwardInvocationSelectorName `。所以是同理的。
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)ASPECTS_ARE_BEING_CALLED, "v@:@");
if (originalImplementation) {
// 將__aspects_forwardInvocation:指向originalImplementation,
// 將originalImplementation添加到Method,以便下次調(diào)用,直接就可以了
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
再來(lái)看看`__ASPECTS_ARE_BEING_CALLED__`這個(gè)方法如何將轉(zhuǎn)發(fā)過(guò)來(lái)的參數(shù)成功的轉(zhuǎn)換為我們需要的參數(shù)。
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;
// 本對(duì)象的AspectsContainer,添加到對(duì)象的aspect
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
// 這個(gè)類的AspectsContainer,添加類上面的aspect
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])) {
// 直接調(diào)用
[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)];
}
用自定的`aliasSelector`替換掉`invocation`的`selector`。然后將`invocation`轉(zhuǎn)化為`AspectInfo `。`AspectInfo `主要包含了`invocation `相關(guān)的信息,比如參數(shù)數(shù)組。如何獲取參數(shù)數(shù)組呢?代碼是通過(guò)為`NSInvocation`寫一個(gè)分類來(lái)實(shí)現(xiàn)。原理就是涉及到類型編碼的那塊。來(lái)看看:
// 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
}
有幾個(gè)基礎(chǔ)的地方。
- @encode:獲取類型編碼:比如`strcmp(argType, @encode(SEL)) == 0`就是判斷參數(shù)`argType`是否為SEL類型。
- WRAP_AND_RETURN:是一個(gè)宏定義,為了將一般類型,比如`Bool`,`Int`轉(zhuǎn)為對(duì)象,然后能夠添加到數(shù)組中。
開(kāi)始調(diào)用通過(guò)宏定義`aspect_invoke`
define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {
[aspect invokeWithInfo:info];
if (aspect.options & AspectOptionAutomaticRemoval) {
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect];
}
}
為什么這里用宏定義來(lái)調(diào)用呢。因?yàn)楹昴軌蜃屛耀@得堆棧信息。
**進(jìn)入到了最后調(diào)用的地方了。**
-
(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.
/**- index: Indices 0 and 1 indicate the hidden arguments self and _cmd, respectively; you should set these values directly with the target and selector properties
*/
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
void *argBuf = NULL;
// 根據(jù)NSInvocation參數(shù)規(guī)則,從第二個(gè)參數(shù)開(kāi)始取
// 參數(shù)處理
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取出參數(shù) [originalInvocation getArgument:argBuf atIndex:idx]; // 給blockInvocation設(shè)置參數(shù) [blockInvocation setArgument:argBuf atIndex:idx];
}
// 調(diào)用
[blockInvocation invokeWithTarget:self.block];if (argBuf != NULL) {
free(argBuf);
}
return YES;
} - index: Indices 0 and 1 indicate the hidden arguments self and _cmd, respectively; you should set these values directly with the target and selector properties
用之前將`block`轉(zhuǎn)為的`blockSignature`初始化`blockSignature`得到`invocation`。然后處理參數(shù),如果參數(shù)`block`中的參數(shù)大于1個(gè),則把包裝成`AspectInfo `。
然后從`originalInvocation `中取出參數(shù)給`blockInvocation `賦值。最后調(diào)用。` [blockInvocation invokeWithTarget:self.block];`這里`Target`設(shè)置為`self.block`。注意`You must set the receiver’s selector and argument values before calling this method`
#### 在子類中將替換的`selector`指向`_objc_msgForward`
Method targetMethod = class_getInstanceMethod(kclass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
由于`kclass`是替換類的子類,所以正常情況下`targetMethod `不會(huì)為空。相當(dāng)雨得到原有的`IMP`,如果原有的`IMP`就是指向的`_objc_msgForward`。則不用處理了。我們已經(jīng)在前面處理過(guò)了。
直接來(lái)看正常流程
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
// 3- 得到替換的SEL
SEL aliasSelector = aspect_aliasForSelector(selector);
// 子類沒(méi)有這個(gè)方法,添加方法到子類
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);
}
// 使用forwardInvocation來(lái)統(tǒng)一處理,將原SEL指向原SEL找不到方法的forwardInvocation的IMP
// We use forwardInvocation to hook in.
// 4- 將需要替換的selector,指向_objc_msgForward,進(jìn)行統(tǒng)一處理
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
先獲取`targetMethodIMP `的類型編碼。然后將我們自定義的`aliasSelector `添加的子類上。最后進(jìn)行替換。`class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);`最后來(lái)看看這個(gè)函數(shù):
static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
// self 成為子類
IMP msgForwardIMP = _objc_msgForward;
// 需要兼容__arm64__,用不同的方式
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) {
// 通過(guò)Try Cathch 捕獲異常
@try {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(encoding, &valueSize, NULL);
if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
methodReturnsStructValue = NO;
}
} @catch (NSException *e) {}
}
if (methodReturnsStructValue) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
endif
return msgForwardIMP;
}
這個(gè)函數(shù)主要是處理`__arm64__ `中`var_list`結(jié)構(gòu)變了。[可參考](https://blog.nelhage.com/2010/10/amd64-and-va_arg)
` BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;`這句話是判斷類型編碼第一個(gè)字符是否為`_C_STRUCT_B `
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'
這個(gè)是對(duì)類型編碼的宏定義。太深了搞起來(lái)太費(fèi)腦子了。大概了解到這個(gè)程度吧。
**替換之后,調(diào)用原有的Method的時(shí)候,就會(huì)消息轉(zhuǎn)發(fā)**
## 寫在最后
> 平時(shí)接觸事物決定了我們的高度。如果整天圍著`UIKit`轉(zhuǎn),技術(shù)也就那樣。居安思危!