iOS開發中的AOP利器 - Aspects 源碼分析(一)

AOP簡介

AOP全名爲 Aspect Oriented Programming- 面向切面編程。AOPOOPObject-Oriented Programing - 面向對象編程)的補充和完善。 OOP引入封裝繼承多態等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。當咱們須要爲分散的對象引入公共行爲的時候,OOP則顯得無能爲力。也就是說,OOP容許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌代碼每每水平地散佈在全部對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其餘類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(cross-cutting)代碼,在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。ios

AOP技術實際上是對OOP設計的對象,利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。所謂「方面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。git

---- 想了解更詳細的AOP思想能夠查看這邊文章 《團隊開發框架實戰—面向切面的編程 AOP》 ,以上總結也是摘自這篇文章。github

舉個例子,咱們須要統計用戶的行爲,看下用戶對app的興趣分佈熱點,此時一般須要在多個控制器的 viewWillAppear:方法中加如處理統計的代碼,這些代碼都是與業務邏輯無關的,並且分散在多個模塊中,此時咱們可利用AOP技術,把這些重複、分散的代碼提取出來成爲一個獨立的模塊。這樣既減小了系統的重複代碼,也下降了模塊的耦合度。好處仍是十分明顯的。編程

Aspects初步認識及使用

這是iOS開發中實現AOP的一個輕量級框架 , 它就提供了兩個接口實現AOP ,這兩個方法都是 NSObject的分類方法數組

//爲某個全部類 對象的selector 進行切面  添加AOP實現
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

//爲某個對象的selector 進行切面  添加AOP實現
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;
複製代碼

例如我要爲全部繼承自UIViewController的對象的viewWillAppear:添加切面實現安全

[UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo  , BOOL animated ){
   NSLog(@"成功進行了切面");
 }error:NULL];
複製代碼

在程序執行完上面這句代碼後,你就成功的對全部控制器的viewWillAppear :方法hook, 在方法執行完原實現後,都會執行上面的Block中的打印 ,方法中返回的協議<AspectToken>對象,可用於移除你添加的hook(調用 協議對象的 -remove方法)。而Aspect的實例方法- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; 是對單一對象的某個方法進行hook , 效果跟例子中的類方法差很少,這裏就不在過多介紹。bash

Aspects內部實現分析

Apects框架涉及的底層只是比較多,建議先了解清楚一下幾隻知識點後再看源碼實現可能會比較容易理解。多線程

  • Block的內部結構及實現原理
  • runtime的消息轉發機制
  • OC中對象的 isa 指針 + KVO的實現原理

因爲這個框架的設計中,對象間關係的關係比較複雜,這裏我先簡單介紹一下,Aspects中定義的類的做用。app

AspectInfo1. 做爲公有協議(面向接口調用) : 爲外界提供了一個協議(抽象接口),用於在定義block時做爲其第一個參數的遵循協議, 方便訪問對象的相關數據。2. 做爲私有對象 : 在原方法selector發起調用時 , 做爲封裝調用參數(實參,用NSInvocation包裝)的對象框架

AspectIdentifier :用於封裝定義hook時傳進來的block,方法的調用者target , selector ,切面時機選項(AspectOptions - 位移枚舉) , 以及在調用完添加hook方法時做爲返回值返回給調用者 ,遵照協議AspectToken 用於移除hook , 執行hook事件的執行處理

AspectTracker : 用於追蹤或記錄曾經hook過的Class(不包活實例對象的hook) , 以防止對同一個集繼承體系的Class對同一個實例方法進行重複hook

AspectsContainer : 用三數組(beforeAspects , insteadAspects , afterAspects)分別記錄對應時機進行 hook 的標識對象 AspectIdentifier ,爲hook 提供數據存儲及支持。

AspectToken : 用於移除hook的一個協議(只有一個方法 :-remove) , AspectIdentifier就是遵循該協議的類

先大概瞭解框架進行hook時對類的處理宏觀處理圖解,更有利於對細節處理的理解及分析。 下圖是對某個Class的Selector進行了hook處理後的類內變化狀況。

Aspects分析.png

Asepcts的核心步驟:把要進行hook的selector的IMP直接更換爲runtime中的消息轉發的IMP (_objc_msgForward_objc_msgForward_stret) , 讓外界調用改selector的時候直接進入到消息轉發 (注意:這裏的原理與熱修復框架JSPatch的原理是同樣的,所以這兩個框架的共存是有問題的),從而調用到方法-forwardInvocation:方法中,此時Class的-forwardInvocation :的實現已經被框架替換爲自定義函數 __ASPECTS_ARE_BEING_CALLED__ , 從而成功進行hook處理。

下面咱們來分析Asepcts的具體實現 咱們先看兩個添加hook處理的方法 , 其中兩個都是NSObjct的分類方法:

//NSObject的類方法
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

//NSObject的實例方法
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
複製代碼

其中這兩個方法都調用了下面函數

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError * __autoreleasing *error) 
複製代碼

上面函數的 形參 id self ,爲何能夠同時接受實例方法的對象和類方法中Class呢?其實理解這個問題的實質我以爲須要理解OC中對對象的定義。咱們看來看下下面有關對象定義的源碼:

其實OC中Class也是對象 ,咱們能夠看看他們三個(id , Class,NSObject *)在runtime中的定義

// Class實際上是 結構體 objc_class * 的指針 
typedef struct objc_class *Class;

// id實際上是 結構體 objc_object * 的指針 別名
typedef struct objc_object *id;

//OC的基類 NSObject 的聲明 - 能夠理解爲其實能夠看作是首地址爲指向  objc_class *  (Isa)指針的內存 均可以看作是對象
@interface NSObject <NSObject> {
     Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}
複製代碼

經過上面的源碼咱們能夠總結出幾點:

  • Class實際上是 結構體 objc_class * 的指針 別名
  • id實際上是 結構體 objc_object * 的指針 別名
  • 結構體objc_object與OC中的 NSObject 的首地址都是指向 isa ,能夠理解爲首地址爲ISA指針的內存均可以稱爲對象。
  • objc_class 是繼承自 objc_object ,也就是說,OC中 Class 也是一個對象。所以框架中不管是hook的實例方法仍是hook的類方法,均可以統一把 調用的對象( ClassNSObject * ) 傳參 給了id 類型。

添加hook的實現

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError * __autoreleasing *error) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);

    __block AspectIdentifier *identifier = nil;
    //自璇鎖 , 保證block中的線程安全
    aspect_performLocked(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            //類型懶加載 利用runtime 的屬性關聯 添加屬性  __aspects__selector -> AspectsContainer
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            //生成hook的對應標識 AspectIdentifier
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                
                // 添加 identifier 到 aspectContainer 的相應數組
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}
複製代碼

上面方法中的加鎖 aspect_performLocked 函數,保證了block中的資源在多線程下讀取安全 ,實現以下

static void aspect_performLocked(dispatch_block_t block) {
    static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
    OSSpinLockLock(&aspect_lock);
    block();
    OSSpinLockUnlock(&aspect_lock);
}
複製代碼

注意 :可是OSSpinLock這個鎖系統提示已通過期了,並且這個鎖在多線程中若是線程的優先級不一樣,會形成鎖沒法釋放等問題,詳細能夠看下這篇文章再也不安全的 OSSpinLock

添加hook工做之一 : 檢查可否 hook

aspect_isSelectorAllowedAndTrack 檢查hook的可行性 , 並利用AspectTracker 處理防止對一個類的某個方法進行重複hook 。這個方法會分別過濾掉不能hook的黑名單方法 (retain , release ,autorelease ,forwardInvocation: ,retain ), 若是是對整個類的某個selector進行 hook (發生在調用Aspects框架的類方法進行 hook), 還會進行一個額外的處理 ,利用AspectTracker檢查 、記錄並追蹤Classhook 狀況,在一個Class 第一次被hook時,在其向上的繼承關係中都會在全局的容器中保存下hook的記錄具體的實現 ,爲了不在一個繼承關係鏈中重複對同一個selector進行hook ,具體能夠看下這段代碼邏輯及註釋

//查看 self 的 isa 指針是不是metaClass , 這下面的處理都是針對 對 整個class的全部實例對象的實例方法進行hook
if (class_isMetaClass(object_getClass(self))) {
    Class klass = [self class];
    //全局變量記錄全部被hook的Class(系統或自定義的類)
    NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
    //獲取類
    Class currentClass = [self class];
    
    do {
        AspectTracker *tracker = swizzledClassesDict[currentClass];
        if ([tracker.selectorNames containsObject:selectorName]) { //證實曾經 對selector hook 過

            //判斷要hook的方法 , 在對應的子類是否有hook過同一個selector ,子類hook過了 ,就不能再對父類hook
            // Find the topmost class for the log.
            if (tracker.parentEntry) {  //證實子類已經對selector hook過了 ,下面的邏輯主要是找出具體那個子類被hook , 該類的 parentEntry = nil
                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) { //這裏表示之前對class的selector進行過hook , 如今重新在該類中對selector定義hook事件
                // hook的已是最頂曾的類了(oc中的子類 例如:UIButton , UIImagView ),進行行過hook,所以會  沒有 parentEntry , 這裏並無執行下面的while語句
                // Already modified and topmost!
                return YES;
            }
        }
        
    }while ((currentClass = class_getSuperclass(currentClass)));

    // 執行到這裏證實 selector 能夠 hook , 在整個向上的繼承體系中(父類)生成hook的記錄  對應關係 : AspectTracker -> 進行hook的Class + selectorName
    currentClass = klass;
    AspectTracker *parentTracker = nil; //實際添加hook的類 沒有這個parentTracker
    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)));
}
複製代碼

添加hook工做之二 : hook定義的參數存儲 + Block 有效性驗證

//相似懶加載 利用runtime 的屬性關聯 添加屬性  __aspects__selector -> AspectsContainer
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//生成hook的對應標識 AspectIdentifier
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
    
    // 添加 identifier 到 aspectContainer 的相應數組
    [aspectContainer addAspect:identifier withOptions:options];

    // Modify the class to allow message interception.
    aspect_prepareClassAndHookSelector(self, selector, error);
}
複製代碼

若是容許hook , 一個 hook的定義對應一個 AspectIdentifier。一個對象(類也是對象)的全部hook都存放在這個對象經過runtime的對象關聯綁定的屬性中 ,該屬性類型爲AspectsContainer,根據hook定義時傳進來的options參數分別加入到 AspectContainer 對應的數組

  1. beforeAspects - selecter執行前進行的hook處理
  2. insteadAspects - 替換調selecter執行hook處理
  3. afterAspects - selecter執行後進行的hook處理

注意:咱們看下AspectContainer中的屬性聲明 , 三個數組都是聲明爲 atomic ,來保證多線層的讀取安全。這也是框架做者在開始時就提示咱們,建議不要對調用頻繁的方法進行hook的緣由之一。

// AspectContainer數組屬性聲明
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
複製代碼

AspectIdentifier 這個類在初始化時還作了selector 和 執行hookBlock的參數校驗

AspectIdentifier的初始化方法:主要是驗證完blockhook sel ector的參數類型是否符合要求後,才完成初始化的操做 , 若是不符合要求直接返nil結束初始化,代碼實現以下:

+ (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); // TODO: check signature compatibility, etc.
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }

    //selector 與 block 參數匹配後 生成 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
    }
    return identifier;
}
複製代碼

AspectIdentifier初始化方法中調用的獲取Block簽名字符的函數:這個函數主要是根據BlockBlock在編譯成C語言後實際上是一個結構體)的內部結構,操做指針的位移數來獲取到簽名參數字符串,並色很生成 NSMethodSignature返回。 更詳細的原理 ,能夠看下我以前寫的《淺析Block的內部結構 及其 如何利用 NSInvocation 進行調用》

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];
}
複製代碼

Blockhook selector的參數校驗函數:主要是獲取selector參數數量方法簽名字符串跟Block的簽名字符串比較它們是否一致,這裏解析一下for循環爲何是從2開始遍歷的

  • block執行調用時所傳的參數: 0 . block自己(encodeType = @?) 1 . 其餘自定義的參數(這裏的Block 索引爲1 的位置爲 id aspectInfo)

  • selector 執行調用時所傳的參數: 0.id object 方法調用者 1.selector 方法本省 2 .其餘自定義的參數

因此這裏要校對的是自定義參數的是否一致,這裏Block的前兩個參數分別是Block自己,以及一個id<ApsectInfo>類型的對象。因此從第二個索引開始比較自定義參數的類型。

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
    NSCParameterAssert(blockSignature);
    NSCParameterAssert(object);
    NSCParameterAssert(selector);

    BOOL signaturesMatch = YES;
    NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
    if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            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.
        // 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:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature]; AspectError(AspectErrorIncompatibleBlockSignature, description); return NO; } return YES; } 複製代碼

添加hook工做之三 : 方法的交換處理

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    
    //獲取klass 獲取進行hook處理的Class,主要是替換 forwardInvocation:方法的 IMP 。
    Class klass = aspect_hookClass(self, error);
    
    //獲取原來方法的IMP
    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.
        //selector的IMP替換爲 消息轉發的IMP
        //aspects__selector的IMP替換爲 最初selector的IMP
        
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) { //判斷klass 是否能響應aliasSelector ,不能的話就添加aliasSelector方法 , 實現爲原來selector的實現IMP
            __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. 讓原來的selector 直接進入消息轉發 forwardInvocaction:
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}
複製代碼

首先獲取到要hookClass , 而後判斷要hookselectorIMP是否是進入 消息轉發的 IMP 是的話就默認已經完成了框架中進行hook的準備工做了。若是不是的話繼續進行 if代碼塊裏的處理邏輯

  1. 添加方法,名字爲 aspects__selector(selector爲要hook的方法名) , 使其 IMP指向selectorIMP
  2. selectorIMP指向消息轉發的IMP, 這是外界調用這個 selector直接進入消息轉發,從而調用到被處理過的 forwardInvocation: 通過處理後 ,外界調用selector是就能夠進入了消息轉發的 forwardInvocation:方法

接下來看下獲取hook Class時的代碼實現

static Class aspect_hookClass(NSObject *self, NSError **error) {
    NSCParameterAssert(self);
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);

    // Already subclassed
    if ([className hasSuffix:AspectsSubclassSuffix]) { //className 若是有 _Aspects_  前綴 , 之前hook作的實例對象
        return baseClass;

        // We swizzle a class object, not a single object.
    }else if (class_isMetaClass(baseClass)) { //self 是 Class
        return aspect_swizzleClassInPlace((Class)self);
    
        // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place. //測試 :對UILabel對象進行kvo後 class == UILabel , get_class == NSKVONotifying_UILabel //Aspect在gitHub上的issues上有人已經解決了KVO衝突的方案 https://github.com/steipete/Aspects/pull/115 }else if (statedClass != baseClass) { //self 是 被KVO的對象 , 須要把 NSKVONotifying_ClassName 的 forwardInvocation: 替換處理 return aspect_swizzleClassInPlace(baseClass); } //self是普通object , 建立一個 aspect__前綴的子類 , 並把 self的 isa 指向新的子類 //不用吧新建的class加入全局class中記錄 // Default case. Create dynamic subclass. const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String; Class subclass = objc_getClass(subclassName); //歷來沒有建立過這個類的話,就重新建立。建立過的話,在runtime中會有記錄,Class相似單例 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__ , __aspects_forwardInvocation -> originalImplementation(forwardInvocation) aspect_swizzleForwardInvocation(subclass); aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass); objc_registerClassPair(subclass); } //修改isa指針 object_setClass(self, subclass); return subclass; } 複製代碼

這個函數其實也是Aspect比較核心的部分,咱們來詳細分析一下方法接下來作的事情

分支else if (class_isMetaClass(baseClass) 證實self 是一個類(Class, 外界調用的是類方法,對整一個類進行hook),調用return aspect_swizzleClassInPlace((Class)self); ,隨後調用到下面兩個函數

static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}

static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    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作相應處理處理

  1. 修改 Class 的方法列表 forwardInvocation: 方法的IMP 指向自定義函數 ASPECTS_ARE_BEING_CALLED

  2. 添加 __aspects_forwardInvocation 方法 , 其IMP 原來的 forwardInvocation指向的IMP

  3. 修改完後把 ClassName 存放到全局的集合中 記錄 證實這個Class已是修改過了 消息轉發IMP了 , 避免之後重複對一個Class進行hook時重複作上面 1 ,2的步驟

若是執行到分支else if (statedClass != baseClass) , 證實self是一個實例對象 , 而且這個實例對象是先被添加了KVO處理 ,再調用Aspects框架添加hook處理的對象 。 注意 :Apsects如今是不支持實例對象先被KVO,再添加hook處理的。程序會提示unrecognized selector sent to instance 而後崩掉 , 框架做者在Demo的測試代碼中也要相關說明 , 也有人在github上給做者提了issue 詳細能夠點擊這裏查看

若是上面那幾個if else都沒有返回到hook class的話,證實要hook的對象是一個普通實例對象 ,不是一個 Class 。接下來將要相似實現KVO的處理。

  1. 嘗試獲取一個類 (名爲 :_ Aspects _實例對象的類名) ,若是系統沒有的話就生成這個類,而且讓這個類繼承自實例對象的類

  2. 調用函數 aspect_swizzleForwardInvocation,作消息轉發方法IMP自定義處理(同上面3個步驟同樣)

  3. 調用 aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass);修改對應實例對象 和 類 的 class 方法 返回對象hook以前Class。保持其行爲與hook以前保持一致,從而不影響到外界的使用。

4.調用objc_registerClassPair(subclass); object_setClass(self, subclass); 把新生成的Class註冊到系統中 ,而且把實例對象的isa指向新的Class

經過生成一個新的Class,並修改實例對象的isa指向新 Class , 這樣處理的目的是,既爲單個實例對象實現了hook處理 , 也不會影響到其餘同類的實例對象 。其實KVO也是經過一樣得原理實現的。

hook具體怎麼執行在第二文章分析

相關文章
相關標籤/搜索