所謂的面向切面編程(AOP),原理就是在不更改正常業務的流程的前提下,經過一個動態代理類,實現對目標對象嵌入的附加的操做。ios
簡單說,就是在不影響咱們如今正常業務的狀況下,對某些類的某些方法嵌入操做。咱們能夠很通俗的理解一個方法能夠有方法前和方法後這兩個切面,固然還能夠把方法執行過程看過一個整的切面去hook。git
在咱們的iOS開發中,AOP的實現方法就是使用Runtime的Swizzling Method改變selector指向的實現,在新的實現中添加新的操做,執行完新實現以後,再處理以前的實現邏輯。github
Aspects是iOS平臺比較成熟的AOP的框架,此次咱們主要來研究一下這個庫的源碼。編程
基於Aspects 1.4.1版本。安全
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;
傳參也很容易理解,selector天然就是咱們要hook的方法,options使咱們要hook的位置,下面具體再說,block是一個回調,也就是咱們所說的要嵌入的代碼邏輯,error就是hook失敗,固然了失敗的緣由較多,咱們下面會提到。ide
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. };
options也是一個枚舉的類型,看一下里面定義的字段就很容易明白了,AspectPositionAfter是表示嵌入的放大要在被hook方法原來邏輯以前以後執行,AspectPositionBefore是以前執行,AspectPositionInstead表示要用嵌入的代碼替換掉以前的邏輯,AspectOptionAutomaticRemoval表示hook執行後,移除hook。函數
由於是NS_OPTIONS類型,可多選。源碼分析
除了上述核心的方法是經過NSObject的Category的方式給出,還有如下幾個類比較重要。.net
AspectsContainer
: 一個對象或者類的全部的Aspects總體狀況
AspectIdentifier
: 一個Aspects的具體內容,這裏主要包含了單個的 aspect 的具體信息,包括執行時機,要執行 block 所須要用到的具體信息:包括方法簽名、參數等等
AspectInfo
: 一個 Aspect 執行環境,主要是 NSInvocation 信息。
Aspects給出的兩個方法最終都是調用了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(^{ // 是否容許hook - if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { //取到sel對應的container (一個類不一樣sel對應不一樣的container?) 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;
每次在添加hook的時候,都會先建立一個AspectIdentifier。
__block是爲了能在下面的block中修改identifier。
aspect_performLocked
是封裝了一個自旋鎖。
在自旋鎖中會有一個if語句來判斷selector是否能被hook。那咱們就先來看一下是否能被hook的判斷方法aspect_isSelectorAllowedAndTrack
黑名單
aspect_isSelectorAllowedAndTrack
方法中維護了一個NSSet,在初始化的時候加入了一些方法名,在源碼中是下面這些。
[NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
這說明了後面這幾個方法是不容許被hook的,若是hook了這些方法會有錯誤的信息提示。
在hook的方法中,dealloc屬於一個特殊狀況,由於這個方法是在對象要被銷燬的時候建立,因此Aspects爲了安全起見,在hook dealloc方法的時候。options只容許時AspectPositionBefore,也就是插入的邏輯只能在dealloc原有邏輯以前處理,不容許替換或者在dealloc以後。
這個就沒啥疑問了,若是咱們hook了一個根本不存在的方法也會有錯誤提示。
方法只容許hook一次(元類相關)
這個有點麻煩,由於從錯誤提示的枚舉來看,他是對應AspectErrorSelectorAlreadyHookedInClassHierarchy
這一項的,從字面意思來看是說方法已經被hook了。
最開始我對這個類的層級不是很明白,個人最初理解的類的層級是父類和子類不能同時hook,通過驗證這種理解是錯誤的。
後來我仔細地看了一下源碼,他的元類判斷中是這麼寫的:
if (class_isMetaClass(object_getClass(self))) {}
判斷的是object_getClass(self),經過runtime的源碼咱們能夠知道,object_getClass獲得的是傳參的isa指針指向的結構,意識就是self若是是對象,object_getClass(self)獲得的是對應的類,若是self是類,那就獲得了元類。
那何時會提示這個錯誤呢,我舉一個例子吧。我建立了一個TestHookViewController
類,繼承自UIViewController
,若是我在TestHookViewController
中像下面這樣寫就會有錯誤提示了。
[[UIViewController class] aspect_hookSelector:@selector(viewWillAppear:) withOptions:0 usingBlock:^(id<AspectInfo> info, BOOL animated) { NSLog(@"%s",__func__); } error:NULL]; [[TestHookViewController class] aspect_hookSelector:@selector(viewWillAppear:) withOptions:0 usingBlock:^(id<AspectInfo> info, BOOL animated) { NSLog(@"%s",__func__); } error:NULL];
固然了,這兩個hook的先後位置不一樣,打印臺輸出的提示也是不同的,雖然都是一個類層級方法只容許hook一次的錯誤緣由。這個你們自行嘗試一下。
if語句裏面就是關於方法是否重複hook的判斷邏輯,這裏牽扯到一個相關類,AspectTracker。咱們如今就來看一下這個類。
雖然是說AspectTracker類,可是代碼結構仍是接着上面的我們說到的位置繼續往下走。
由於AspectTracker主要就是用在方法只容許hook一次的判斷中。
Aspects維護了一個字典,來儲存被hook方法的類和對應的AspectTracker。
Class currentClass = [self class]; AspectTracker *tracker = swizzledClassesDict[currentClass];
經過上面的方式取到對應的AspectTracker。這裏提一句,這裏的代碼咱們應該先看下面的,要先了解AspectTracker是怎麼存儲到字典裏面的。
這裏咱們要看下面這個do-while循環
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)));
若是最開始沒有tracker,會初始化一個,而後存到字典中。最開始的時候subclassTracker爲nil,因此selector會add到tracker.selectorNames。
而後currentClass從新賦值
currentClass = class_getSuperclass(currentClass)
再次執行do邏輯裏面的代碼,此次subclassTracker就有值了(上一次循環的tracker),就會執行[tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
咱們進到addSubclassTracker
源碼中能夠看到,tracker被存到selectorNamesToSubclassTrackers
這個字典中,關鍵的是這個字典的key是selectorName,value是一個集合,tracker是放在這個集合裏面的。爲何要經過集合來存tracker呢?
由於這裏是有子類的狀況的,一個類的子類可能有多個,若是在不一樣的子類中hook了這個父類的一個方法,也就是父類中的這一個selector被屢次hook,因此也會有不一樣的tracker,因此使用一個集合來儲存。
其實說到這個地方你們就差很少能夠理解了,若是知足了if (class_isMetaClass(object_getClass(self)))
這個判斷,咱們會把這個類hook的方法經過封裝爲AspectTracker
來進行記錄,固然包括他的層層父類,都對對應一個AspectTracker
,並且父類中的還會記錄子類中hook的方法。這部分代碼最好是debug跟一下,會明顯一點。
上面咱們先看了tracker是怎樣被存起來的,接來下再來看關於只能被hook一次的判斷。
首先要判斷子類中是否hook
if ([tracker subclassHasHookedSelectorName:selectorName]) { //內部省略 }
subclassHasHookedSelectorName
內部實現很簡單
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName { return self.selectorNamesToSubclassTrackers[selectorName] != nil; }
就是查詢一下selectorNamesToSubclassTrackers這個字典中,經過seletorName是否能取到值,上面已經說過了,這個字典中經過key:selectorName value: set的方法儲存了子類的tracker。
若是能取到值,就說明子類中已經hook了這個方法了,父類中就不能在hook。
若是沒能取到值,說明當前類可能就是個子類,此時須要看一下他的父類中是否hook了這個selector,因此就會執行下面的的do-while循環。 此處的代碼就不展現了。
但這個位置初步的關於方法可否被hook就已經判斷完了。若是能夠seletor能夠被hook,繼續if裏面的代碼。
咱們直接說Swizzling Method這一最重要的邏輯吧。
Swizzling Method主要有兩部分,一個是對對象的 forwardInvocation 進行 swizzling,另外一個是對傳入的 selector 進行 swizzling。
咱們來看aspect_prepareClassAndHookSelector
方法的源碼。
首先是Class klass = aspect_hookClass(self, error);
static Class aspect_hookClass(NSObject *self, NSError **error) { NSCParameterAssert(self); Class statedClass = self.class; Class baseClass = object_getClass(self); NSString *className = NSStringFromClass(baseClass); // // 省略一部分代碼 // // 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方法 aspect_swizzleForwardInvocation(subclass); // subclass的class方法交替換 替換爲statedClass的class方法 subclass元類也替換 aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass); objc_registerClassPair(subclass); } // 改變當前類的isa指針指向 object_setClass(self, subclass); return subclass; }
咱們從源碼中能夠看出邏輯,主要是動態建立了一個subclass,名爲subclass,其實最終把咱們hook的類的isa指針指向了這個subclass,實爲是一個父類。
aspect_swizzleForwardInvocation(subclass);
上面這個方法是替換了forwardInvocation:
方法。
固然了,也是替換的這個subclass的forwardInvocation:
方法,把forwardInvocation:
替換爲__ASPECTS_ARE_BEING_CALLED__
這個方法,主要的hook後的代碼執行處理邏輯都在這個__ASPECTS_ARE_BEING_CALLED__
中。
在替換了aspect_hookClass
方法以後,同時修改了 subclass以及其subclass metaclass的class方法,使他返回當前對象的class。這個地方有點繞,其實最終目的就是把全部的swizzling都放到了這個subclass中處理,不影響原來的類,並且對於外部的使用者,又能夠把它當作原對象使用。
執行完aspect_hookClass
完以後,forwardInvocation:
方法已經被替換,下面會執行swizzling selector 的代碼。
在swizzling selector的時候,將selector指向了消息轉發IMP,同時生成一個aliasSelector,指向原方法的IMP。
這裏代碼就不往外粘了。
其實上面已經把整個過程分析完了,咱們也知道,最後轉發的代碼最終會在__ASPECTS_ARE_BEING_CALLED__
函數的處理中。因此最後咱們來看看這個函數就能夠了。
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]; //根據aliasSelector找到以前的邏輯 執行 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)]; }
從源碼中很容易看出來,分別處理不一樣的hook點,而後中間有根據
aliasSelector找到以前方法的實現,而後執行。
初步的源碼分析就是這個樣子,沒有太關注一些細節,也存在一些本身如今還不是很熟悉的處理方式,畢竟涉及到太多的swizzling,消息轉發一類的方法,這一塊的只是須要後期在多研究runtime來提升。
代碼中有一些其餘的比較小的方法沒有講到,你們本身自行看一下。
https://wereadteam.github.io/...