(長文預警)面向切面 Aspects 源碼閱讀

前言

AOP(Aspect-oriented programming) 也稱之爲 「面向切面編程」, 是一種經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。簡單來講能夠作到 業務隔離解耦 等等效果。AOP 技術在__JAVA__ 的 Spring 框架中已經提供了很是全面成熟的解決方案。然而 iOS 等移動端在這方面的運用並非不少,可是不妨礙它涌現出很是出色的三方庫,好比,咱們接下來要說的三方庫 Aspects .git

那麼咱們何時使用 AOP 比較適合呢github

  • 當咱們執行某種方法時候,對方法進行安全檢查 (例如,NSArray的數組越界問題)
  • 對某些操做進行日誌記錄
  • 對購物車進行交互時候,根據用戶的操做,觸發建議或者提示
  • 激進一些,也能夠用來去基類繼承,例如筆者的架構實例:NonBaseClass-MVVM-ReactiveObjc (乾貨帶代碼)

你們會說,傳統的 OOP(Object Oriented Programming) 即面向對象編程,也徹底可以實現這些功能。 是的,沒錯,可是一個好的 OOP 架構應該是單一職責的,添加額外的切面需求意味着破壞了單一職責。例如,一個 Module 僅僅負責訂單業務,可是你其添加了安全檢查,日誌記錄,建議提示等等功能,這個 Module 會變得難以理解和維護,並且整個應用都將充斥着 日誌,安全檢查 等等邏輯,想一想都頭皮發麻。編程

頭皮發麻

AOP 正是解決這一系列問題的良藥。swift

切面

__Aspects__基礎用法

__Aspects__使用起來也是很是簡單,只須要使用兩個簡單的接口,同時支持 類Hook實例Hook,提供了更細緻的操做數組

/// Adds a block of code before/instead/after the current `selector` for a specific class.
+ (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;
複製代碼

例如,咱們須要統計用戶進入某個 ViewController 的次數安全

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
複製代碼

今後,咱們沒必要在基類中添加醜陋的代碼了bash

Aspects架構剖析

架構

swizzledClassesDict 全局字典

訪問接口架構

static NSMutableDictionary *aspect_getSwizzledClassesDict() {
    static NSMutableDictionary *swizzledClassesDict;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        swizzledClassesDict = [NSMutableDictionary new];
    });
    return swizzledClassesDict;
}
複製代碼

存儲對象類型 <Class : AspectTracker *>
此全局字典記錄了對Hook的類及其父類的追蹤信息對象 AspectTrackerapp

swizzledClasses 全局集合

訪問接口框架

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

swizzledClasses 是一個全局集合,存儲對象類型 被Hook的對象類名都會被存儲在此容器中

AspectBlockRef

Aspects 中的 AspectBlockRef 也就是咱們使用接口中的參數 usingBlock 中的Block,在上述的例子中以下形式

^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
}
複製代碼

AspectBlockRef 的源代碼以下

typedef NS_OPTIONS(int, AspectBlockFlags) {
	AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
	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;
複製代碼

看上去有點複雜,可是,咱們一直使用的Block就是這樣的結構體,在這裏 AspectBlockRef 其實就是 ___GloablBlock__類型,AspectBlockRef 只是名字改了一下而已,本質上就是 Block 很幸運,蘋果開源了Block的相關代碼 :Block實現源碼傳送門 咱們得以一窺究竟

/////////////////////// Block_private.h

// 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
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

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

其中 flags 表明着Block的操做數

// AspectBlockRef
AspectBlockFlags flags;   
// Block_private.h
volatile int32_t flags; // contains ref count
複製代碼

Aspects 中只有兩種flag

AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature          = (1 << 30)
複製代碼

對應 Block 定義枚舉中的

BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
複製代碼
  • 當Block被Copy時 flag & AspectBlockFlagsHasCopyDisposeHelpers 爲真時,Block佈局中將會添加 Block_descriptor_2
  • 當Block帶有方法簽名時 flag & AspectBlockFlagsHasSignature 爲真時,Block佈局中存在Block_descriptor_3

invoke 函數指針也只是第一個參數由泛型變成了___AspectBlock__而已

// AspectBlockRef
void (__unused *invoke)(struct _AspectBlock *block, ...); // Block_private.h void (*invoke)(void *, ...); 複製代碼

扯遠了,打住。

AspectToken

AspectToken

Aspects 內定義的協議,用來撤銷Hook

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
複製代碼

Hook方法會返回一個遵循 AspectToken 協議的方法,要取消對應的Hook,只須要調用代理對象的協議方法 remove就能夠了

AspectInfo

AspectInfo

AspectInfo 對象遵循了 __AspectInfo__協議(同名協議),表明着切面信息,也是上文 AspectBlockRef 中的首個參數

  • instance :當前被Hook的實例
  • originalInvocation :當前被Hook 原始的 NSInvocation 對象
  • arguments:全部方法的參數,一個計算屬性,被調用的時候纔會有值

這裏特別提一下,參數是從 NSInvocation 中拿到的

NSMutableArray *argumentsArray = [NSMutableArray array];
for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
	[argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null];
}
複製代碼

這裏參數下標是從2開始的,由於下標 0,1,已經分別對應了 __消息接受對象__和 selector, 對於參數裝箱細節,能夠細看 Aspects 的內部 NSInvocation 分類

AspectIdentifier

AspectIdentifier

實際上 AspectIdentifier 就是 aspect_hookSelector 函數的返回值

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
複製代碼

AspectIdentifier 提供了remove 方法的實現,然而我並無在源碼中見到 AspectIdentifier 有聲明遵循 ____協議

如下方法對須要hook的類進行信息封裝操做,方法內部對usingBlock參數中的Block進行適配檢查

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error 
{
    // 獲取Block的方法簽名
    NSMethodSignature *blockSignature =   aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
    // 兼容性檢測
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }
    // hook 信息封裝
    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;
}
複製代碼
AspectsContainer

AspectsContainer

AspectsContainer 顧名思義,也就是切面的容器類,內部根據不一樣的option選項將 AspectIdentifier 放入不一樣的容器內

  • NSArray *beforeAspects 對應 AspectPositionBefore 選項
  • NSArray *insteadAspects 對應 AspectPositionInstead 選項
  • NSArray *afterAspects 對應 AspectPositionAfter 選項

要注意的是 __Asepcts__經過 aspect_getContainerForObject 方法從關聯的對象獲取

static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
     // 獲取方法別名(原方法添加 aspects_ 前綴)
    SEL aliasSelector = aspect_aliasForSelector(selector);
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    if (!aspectContainer) {
        aspectContainer = [AspectsContainer new];
       // 以方法別名來關聯 AspectsContainer 容器對象
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}
複製代碼
AspectTracker

AspectTracker

AspectTracker 表明着對切面的追蹤,存儲在全局字典 swizzledClassesDict 中,從子類向上追蹤記錄信息

  • selectorNames 記錄當前被追蹤的類須要hook的方法名
  • selectorNamesToSubclassTrackers 經過addSubclassTracker: hookingSelectorName記錄子類的 AspectTracker 對象
// Add the selector as being modified.
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)));
複製代碼
Aspects核心解析

核心

方法入口

方法入口

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add((id)self, selector, options, block, error);
}

/// @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 {
    return aspect_add(self, selector, options, block, error);
}
複製代碼

Aspects 既支持對類的 Hook,也支持對實例的Hook, 其核心在於 aspect_add 方法

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

    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        // 判斷是否容許Hook類
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            // 獲取關聯容器
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            // hook信息封裝成AspectIdentifier對象
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

                // hook 核心操做
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}
複製代碼

基本上 aspect_add 核心操做有三步

  • __aspect_isSelectorAllowedAndTrack__方法 判斷是否容許 Hook
  • hook信息封裝成AspectIdentifier對象
  • aspect_prepareClassAndHookSelector 方法執行 Hook 操做(核心操做)

接下來一步步進行解析

aspect_isSelectorAllowedAndTrack 判斷是否容許Hook

一、黑名單過濾

static NSSet *disallowedSelectorList;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
    disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
});

NSString *selectorName = NSStringFromSelector(selector);
if ([disallowedSelectorList containsObject:selectorName]) {
    ......
    return NO;
}
複製代碼

特殊方法不容許hook

二、dealloc 方法hook只容許使用 AspectPositionBefore 選項

AspectOptions position = options&AspectPositionFilter;
if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
    ......
    return NO;
}
複製代碼

三、過濾沒法響應的方法

if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
    return NO;
}
複製代碼

四、實例和類過濾

在此以前插播下元類的概念

元類的概念

元類的定義:元類是類對象的類

還有須要注意的一點,class_isMetaClass(object_getClass(self)) 使用的是 object_getClass 方法 而不是相似 [obj class]的方法 二者有何區別呢?

object_getClass [obj class]
實例對象 isa指針指向 isa指針指向
類對象 isa指針指向 對象自己
元類對象 isa指針指向 對象自己

好了,接下來咱們看下過濾的代碼

if (class_isMetaClass(object_getClass(self))) {
	......
}else{
	return YES;
}
複製代碼

在這裏,筆者認爲 class_isMetaClass(object_getClass(self)) 做用是判斷 hook 的對象是 實例 仍是

NSObject *obj = [[NSObject alloc] init];
[obj aspect_hookSelector:@selector(copy) withOptions:AspectPositionAfter usingBlock:^(id <AspectInfo> info){
} error:nil];

Class getClass = object_getClass(self);
BOOL isMeta = class_isMetaClass(getClass); // 打印 NO
複製代碼

若是是實例對象的話 getClass 也就是isa指針,指向了對應的類,而後 class_isMetaClass判斷天然是爲NO

[NSObject aspect_hookSelector:@selector(copy) withOptions:AspectPositionAfter usingBlock:^(id <AspectInfo> info){

} error:nil];

Class getClass = object_getClass(self);
BOOL isMeta = class_isMetaClass(getClass); // 打印 YES
複製代碼

若是是類對象的話 getClass 也就是isa指針,指向了對應的元類,而後 class_isMetaClass判斷天然是爲YES

若是class_isMetaClass(object_getClass(self))返回YES,也就是說咱們 Hook 的對象是類對象的話

Class klass = [self class]; NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); Class currentClass = [self class]; // 已經hook過的方法再也不進行重複hook AspectTracker *tracker = swizzledClassesDict[currentClass]; if ([tracker subclassHasHookedSelectorName:selectorName]) {
    return NO;
}

// 向上查找父類
do {
    tracker = swizzledClassesDict[currentClass];
    if ([tracker.selectorNames containsObject:selectorName]) {
        if (klass == currentClass) {
            // 若是是已經遍歷到了父類頂層
            return YES;
        }
        // 已經hook過的方法再也不進行重複hook
        return NO;
    }
} while ((currentClass = class_getSuperclass(currentClass)));

// 向上查找父類,生成 AspectTracker 信息
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)));
複製代碼

因此,hook 實例方法不須要向上遍歷父類方法,這也符合直覺和邏輯

Aspects hook 以前作了很是健全的前置檢查,很是值得學習!

OK,下一個tips

Hook 信息封裝成AspectIdentifier對象

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {

    NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
    if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
        return nil;
    }

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

方法首先經過aspect_blockMethodSignature提取 Block 的方法簽名

static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
    AspectBlockRef layout = (__bridge void *)block;
    // 經過判斷block的flags 來肯定Block是否存在方法簽名
    if (!(layout->flags & AspectBlockFlagsHasSignature)) {
        return nil;
    }
    // 獲取 descriptor 
    void *desc = layout->descriptor;
    /* 計算內存地址偏移量來肯定 signature 的位置 */
    // 加上 reserved 和 size的偏移量 (類型皆爲unsigned long int 因此 乘以 2)
    desc += 2 * sizeof(unsigned long int);
    // 若是有 copy 和 dispose 的 flag 須要 加上對應偏移量
    if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
        desc += 2 * sizeof(void *);
    }
    if (!desc) {
        return nil;
    }
    // 獲取到了真正的方法簽名
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}
複製代碼

漲姿式了,經過如上代碼咱們知道了如何得到Block的方法簽名, 咱們能夠先理解消化本文解釋 AspectBlockRef 的內存佈局,而後再來理解這段代碼

至於代碼中的二級指針(*(const char **)desc)

// descriptor 是一個結構體指針,保存的是結構體的起始地址
struct {
    const char *signature;
} *descriptor;
// 結構體指針 賦值給 泛型指針desc
void *desc = layout->descriptor;
//  當咱們獲取到真正的方法簽名,此時 desc 已經指向了 signature 所在的地址
//  signature 的類型是 const char *,那麼 signature 的地址天然就是const char **咯
//  最後咱們要取 desc 指針中的值,也就是 *obj 咯
const char ** obj = (const char **)desc;
const char *signature = *obj;
複製代碼

恩,沒毛病,下一個Tips, 也是咱們的重頭戲

aspect_prepareClassAndHookSelector 方法執行 Hook 操做

咱們來看下aspect_prepareClassAndHookSelector的源碼

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {

    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]) {
            __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
        }

        // We use forwardInvocation to hook in.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
    }
}
複製代碼

這裏源碼中比較重要和核心的在於 aspect_hookClass 方法

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

    // 已經子類化過了,也就是已經添加過子類後綴 「_Aspects_」
	if ([className hasSuffix:AspectsSubclassSuffix]) {
		return baseClass;
	}
    // 若是 Hook 的是 類對象,那麼混寫 類對象的ForwardInvocation方法 (類對象的指針指向自己)
    else if (class_isMetaClass(baseClass)) {
        return aspect_swizzleClassInPlace((Class)self);
    }
    // 若是 Hook 的是 一個 已經被kvo子類化的實例對象,咱們須要混寫它的 metaClass 的ForwardInvocation方法
    else if (statedClass != baseClass) {
        return aspect_swizzleClassInPlace(baseClass);
    }

    // 混寫實例對象

   // 添加 默認後綴 _Aspects_ 
	const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
   // 獲取 添加默認後綴後的類
	Class subclass = objc_getClass(subclassName);

	if (subclass == nil) {
    // 若是尚未動態建立過子類
		subclass = objc_allocateClassPair(baseClass, subclassName, 0);
		if (subclass == nil) {
            return nil;
        }
        // 混寫 forwardInvocation:方法
		aspect_swizzleForwardInvocation(subclass);
        // 替換 class 的 IMP 指針
        // subClass.class = statedClass
		aspect_hookedGetClass(subclass, statedClass);
        // subClass.isa.class = statedClass 
		aspect_hookedGetClass(object_getClass(subclass), statedClass);
        // 註冊新類
		objc_registerClassPair(subclass);
	}
    // 複寫 isa 指針,指向新的類
	object_setClass(self, subclass);
	return subclass;
}
複製代碼

值得注意的是

// 若是 Hook 的是 類對象,那麼混寫 類對象 (類對象的指針指向自己)
Class baseClass = object_getClass(self);
else if (class_isMetaClass(baseClass)) {
    return aspect_swizzleClassInPlace((Class)self);
}
// 若是 Hook 的是 一個 已經被kvo子類化的實例對象,咱們須要混寫它的 metaClass
else if (statedClass != baseClass) {
    return aspect_swizzleClassInPlace(baseClass);
}
複製代碼

class_isMetaClass(baseClass)前面已經說過了,是用來判斷是否 hook 的是類對象,那麼 statedClass != baseClass 是什麼含義呢

首先咱們得要先知道 KVO 實現的原理: 當觀察某對象 A 時,KVO 機制動態建立一個對象A當前類的子類,併爲這個新的子類重寫了被觀察屬性 keyPath 的 setter 方法

從前文咱們知道,實例對象 object_getClass[obj class] 的指向是一致的,當hook被KVO子類化的實例時候,實例對象的isa指針的指向 和 class 的指向纔會不一致

咱們還學會了動態建立一個類的流程

  • objc_allocateClassPair
  • class_addMethod
  • class_addIvar
  • objc_registerClassPair

小結:對類對象的hook是經過混寫類對象的 forwardInvocation 方法來實現,對實例對象的 hook 是經過子類化,而後混寫子類的 forwardInvocation 來實現的

emmmmm, 接下來咱們聊一聊混寫__forwardInvocation__ 的一些細節

aspect_swizzleForwardInvocation 混寫 forwardInvocation

方法轉發流程
方法轉發流程,這裏就不贅述, 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@:@");
    }
}
複製代碼

上述代碼將類的 forwardInvocation 方法 IMP 替換成 __ ASPECTS_ARE_BEING_CALLED __ ,原來的 IMP 與 AspectsForwardInvocationSelectorName 相關聯了

接下來咱們看看 __ ASPECTS_ARE_BEING_CALLED __ 方法

static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *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) {
        // 執行 insteadAspects 中的操做
        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 hooks.
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);

   
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);

        // 如何hook對象不響應 aliasSelector 那麼執行原有的 forwardInvocation
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            // 若是類沒有實現 forwardInvocation 方法的話,將會拋出 doesNotRecognizeSelector 錯誤
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }

    // 移除 aspectsToRemove 中的 AspectIdentifier,並執行 AspectIdentifier 的 remove 方法
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
複製代碼

上述代碼執行了對應的 beforeAspects,insteadAspects,afterAspects 若是有insteadAspects 操做則執行insteadAspects 操做,不然執行 aliasSelector, 若不響應 aliasSelector, 那麼將執行 hook 以前的 forwardInvocation的方法,沒有實現 forwardInvocation那麼將拋出 doesNotRecognizeSelector 異常

咱們再來看看 aspect_invoke 執行切面的細節

aspect_invoke
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
    [aspect invokeWithInfo:info];\
    if (aspect.options & AspectOptionAutomaticRemoval) { \
        aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
    } \
}
複製代碼

宏定義執行了__AspectIdentifier__ 的 invokeWithInfo:方法

- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;

    // 檢查參數是否適配,已是第二次檢查了
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        return NO;
    }

    // 設置參數
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    
    // 遍歷 originalInvocation 參數,將參數值複製到 blockInvocation
	void *argBuf = NULL;
    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))) {
			return NO;
		}
        
		[originalInvocation getArgument:argBuf atIndex:idx];
		[blockInvocation setArgument:argBuf atIndex:idx];
    }
    // 執行 blockInvocation
    [blockInvocation invokeWithTarget:self.block];
    
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}
複製代碼

這裏比較有意思的是 [blockInvocation setArgument:&info atIndex:1]; 按照正常來講 參數下標0 和 1 分別是 target, selector

打印 blockInvocation 的方法簽名,發現參數下標爲 1 類型 並非 selector ,而是 id< AspectInfo > 有知道的大佬但願可以指出提點下

完結,撒花

還有什麼?

  • aspect_remove 對Hook的清理操做,這裏就不贅述了

  • 有對實踐感興趣的話,能夠看筆者粗淺的架構實例:NonBaseClass-MVVM-ReactiveObjc (乾貨帶代碼)

  • 最後祝願你們新年快樂,過個好年,且行且珍惜,麼麼噠😘

相關文章
相關標籤/搜索