Aspects的源碼學習,我學到的有幾下幾點
- Objective-C Runtime
- 理解OC的消息分發機制
- KVO中的指針交換技術
- Block 在內存中的數據結構
- const 的修飾區別
- block 中常量在特定情況下的三種處理方法
- 斷言語句,
- 自旋鎖 使用注意
-
_objc_msgForward_stret 和 _objc_msgForward 前者存在的必要
- Type Encoding
https://www.cnblogs.com/DafaRan/p/8192069.html
簡介
Aspects是一個面向切面編程的庫。
如果想深入了解iOS Runtime中的消息發送機制,Aspects的源碼是值得分析的。
項目主頁
Aspects
整體分析
閱讀Aspects的源碼需要以下知識作為基礎
- Objective-C Runtime
- 理解OC的消息分發機制
- KVO中的指針交換技術
閱讀本文之前,建議應該先斷點調試下Aspects的Demo,了解大致的過程。
核心實現
Aspects的核心實現就是利用Runtime中的消息分發機制如圖:
Aspects通過把selector的方法替換為msg_forward方法轉發 轉而調用 forwardInvocation(forwardInvocation的實現被Aspects替換,將原來的方法實現與添加的實現組合在了一起)
核心源碼分析
這是Aspects 面向切面編程的入口方法
- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add(self, selector, options, block, error); }復制代碼
這段代碼可以分三部分來看
- aspect_isSelectorAllowedAndTrack 這個方法 對父子類同時hook一個方法進行了一些限制
- aspect_getContainerForObject 通過Runtime添加關聯值的方式 管理hook的方法
- aspect_prepareClassAndHookSelector 這是核心的實現,涉及到動態生成子類,改變isa指針的指向,改變方法的實現 一系列操作
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)) { //一個實例 只有一個container //這是區分實例對象和類對象的關鍵 //實例對象可以有很多個,但是同一個類的類對象只能有一個 AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); //原來的selector block identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { //container 里 存有 identifier (selector,block) [aspectContainer addAspect:identifier withOptions:options]; // Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error); } } }); return identifier; }復制代碼
核心方法
aspect_prepareClassAndHookSelector這是核心的實現,涉及到動態生成子類,改變isa指針,改變方法的實現 一系列操作
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert(selector); //動態創建子類,改變forwardInvocation方法的實現 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]) { //子類的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); } //selector方法替換為_objc_msgForward // We use forwardInvocation to hook in. class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } }復制代碼
動態生成子類,改變isa指針
#pragma mark - Hook Class static Class aspect_hookClass(NSObject *self, NSError **error) { NSCParameterAssert(self); //這里可以思考一下 class 方法 和 isa 的區別 //[self class] KVO可能改變了isa指針的指向 Class statedClass = self.class; // object_getClass 能准確的找到isa指針 Class baseClass = object_getClass(self); NSString *className = NSStringFromClass(baseClass); // Already subclassed //如果已經子類化了 就返回 if ([className hasSuffix:AspectsSubclassSuffix]) { return baseClass; //如果是類 就改掉類的forwardInvocation 而不是一個子類對象 // We swizzle a class object, not a single object. }else if (class_isMetaClass(baseClass)) { return aspect_swizzleClassInPlace((Class)self); //考慮到KVO,KVO的底層實現,交換了isa指針 // 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; } // forwardInvocation 替換成 (IMP)_ASPECTS_ARE_BEING_CALLED__ aspect_swizzleForwardInvocation(subclass); //子類的class方法返回當前被hook的對象的class aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass); objc_registerClassPair(subclass); } //將當前self設置為子類,這里其實只是更改了self的isa指針而已, 這里hook了子類的forwardInvocation方法,再次使用當前類時,其實是使用了子類的forwardInvocation方法。 object_setClass(self, subclass); return subclass; }復制代碼
作者:Junyiii
鏈接:https://juejin.im/post/58f6d1675c497d006ca638fe
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
Aspects初始化工作核心部分的解析
aspect_add方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
//聲明AspectIdentifier實例
__block AspectIdentifier *identifier = nil;
aspect_performLocked(^{
//判斷當前XX方法是否允許被Hook,1."retain"、"release"、"autorelease"、"forwardInvocation"這幾個方法是不被允許的,所謂的黑名單。2.如果方法是dealloc,則他的切入點必須是Before。3.判斷當前實例對象和類對象是否能響應方法4.是否是類對象,如果是則判斷繼承體系中方法是否已經被Hook,而實例則不用。
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//獲得當前aspects__XX方法的AspectsContainer容器
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//初始化AspectIdentifier變量,方法內部通過toll-free bridged獲取Block方法簽名,並判斷其兼容性
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//通過options選項分別添加到容器中的beforeAspects,insteadAspects,afterAspects這三個數組
[aspectContainer addAspect:identifier withOptions:options];
//HookSelector的過程和HookClass的過程
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
|
HookClass過程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// 如果類名有_Aspects_前綴,說明Class已被Hook
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);
}
// 默認則會動態創建一個子類
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
//實現替換當前類forwardInvocation方法的實現為__ASPECTS_ARE_BEING_CALLED__
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;
}
|
HookSelector過程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
//HookClass過程
Class klass = aspect_hookClass(self, error);
//此時的klass類為剛創建的具有_Aspects_后綴的子類,在創建的時候指定類他的父類,所以我們可以獲取到selector這個方法
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
//判斷是否為消息轉發
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
//獲得原生方法的類型編碼
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
//為klass添加aspects__XX方法,方法的實現為原生方法的實現。
__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);
}
// 將原生方法實現替換為_objc_msgForward或_objc_msgForward_stret,用來實現消息轉發
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
|
Aspects執行工作核心部分的解析
當我們正式向某個接受者發送消息的時候,會進行消息轉發,而之前HookClass的過程當中我們已經對forwardInvocation的實現替換為了__ASPECTS_ARE_BEING_CALLED__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
//獲取原始方法XX
SEL originalSelector = invocation.selector;
//獲取含有前綴的方法aspects_XX
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
//替換Sel
invocation.selector = aliasSelector;
//獲得實例對象容器
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
//獲得類對象容器
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
//初始化AspectInfo,傳入self、invocation參數
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// 執行before切入點的調用
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// 執行Instead切入點的調用,判斷當前insteadAspects是否有數據,如果沒有數據則判斷當前繼承鏈是否能響應aspects_XX方法,如果能,則直接調用,此時的aspects_XX則為原生的實現。
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切入點的調用
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];
}
}
// 對需要被移除的切面執行remove方法
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
|
執行block的代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
- (BOOL)invokeWithInfo:(id)info {
//根據blockSignature獲取Invocation
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
//獲取原生方法的Invocation
NSInvocation *originalInvocation = info.originalInvocation;
//獲取blockInvocation參數個數
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// 判斷blockInvocation參數個數是否大於originalInvocation參數個數
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
// blockInvocation給索引為1的參數賦值
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
//當所以大於1的時候進行遍歷,把原生的參數值賦值給相應的blockInvocation中的參數
void *argBuf = NULL;
for (NSUInteger idx = 2; idx
|
流程圖:
http://ios.jobbole.com/84522/