iOS 如何實現Aspect Oriented Programming (上)

前言

在「Runtime病院」住院的後兩天,分析了一下AOP的實現原理。「出院」後,發現Aspect庫尚未詳細分析,因而就有了這篇文章,今天就來講說iOS 是如何實現Aspect Oriented Programming。php

目錄

  • 1.Aspect Oriented Programming簡介
  • 2.什麼是Aspects
  • 3.Aspects 中4個基本類 解析
  • 4.Aspects hook前的準備工做
  • 5.Aspects hook過程詳解
  • 6.關於Aspects的一些 「坑」

一.Aspect Oriented Programming簡介

面向切面的程序設計(aspect-oriented programming,AOP,又譯做面向方面的程序設計觀點導向編程剖面導向程序設計)是計算機科學中的一個術語,指一種程序設計範型。該範型以一種稱爲側面(aspect,又譯做方面)的語言構造爲基礎,側面是一種新的模塊化機制,用來描述分散在對象)、)或函數)中的橫切關注點(crosscutting concern)。html

側面的概念源於對面向對象的程序設計的改進,但並不僅限於此,它還能夠用來改進傳統的函數。與側面相關的編程概念還包括元對象協議、主題(subject)、混入(mixin)和委託。ios

AOP經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。git

OOP(面向對象編程)針對業務處理過程的實體及其屬性行爲進行抽象封裝,以得到更加清晰高效的邏輯單元劃分。github

AOP則是針對業務處理過程當中的切面進行提取,它所面對的是處理過程當中的某個步驟階段,以得到邏輯過程當中各部分之間低耦合性的隔離效果編程

OOP和AOP屬於兩個不一樣的「思考方式」。OOP專一於對象的屬性和行爲的封裝,AOP專一於處理某個步驟和階段的,從中進行切面的提取。api

舉個例子,若是有一個判斷權限的需求,OOP的作法確定是在每一個操做前都加入權限判斷。那日誌記錄怎麼辦?在每一個方法的開始結束的地方都加上日誌記錄。AOP就是把這些重複的邏輯和操做,提取出來,運用動態代理,實現這些模塊的解耦。OOP和AOP不是互斥,而是相互配合。數組

在iOS裏面使用AOP進行編程,能夠實現非侵入。不須要更改以前的代碼邏輯,就能加入新的功能。主要用來處理一些具備橫切性質的系統性服務,如日誌記錄、權限管理、緩存、對象池管理等。緩存

二. 什麼是Aspects

Aspects是一個輕量級的面向切面編程的庫。它能容許你在每個類和每個實例中存在的方法裏面加入任何代碼。能夠在如下切入點插入代碼:before(在原始的方法前執行) / instead(替換原始的方法執行) / after(在原始的方法後執行,默認)。經過Runtime消息轉發實現Hook。Aspects會自動的調用super方法,使用method swizzling起來會更加方便。安全

這個庫很穩定,目前用在數百款app上了。它也是PSPDFKit的一部分,PSPDFKit是一個iOS 看PDF的framework庫。做者最終決定把它開源出來。

三.Aspects 中4個基本類 解析

咱們從頭文件開始看起。

1.Aspects.h
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.
};複製代碼

在頭文件中定義了一個枚舉。這個枚舉裏面是調用切片方法的時機。默認是AspectPositionAfter在原方法執行完以後調用。AspectPositionInstead是替換原方法。AspectPositionBefore是在原方法以前調用切片方法。AspectOptionAutomaticRemoval是在hook執行完自動移除。

@protocol AspectToken <NSObject>

- (BOOL)remove;

@end複製代碼

定義了一個AspectToken的協議,這裏的Aspect Token是隱式的,容許咱們調用remove去撤銷一個hook。remove方法返回YES表明撤銷成功,返回NO就撤銷失敗。

@protocol AspectInfo <NSObject>

- (id)instance;
- (NSInvocation *)originalInvocation;
- (NSArray *)arguments;

@end複製代碼

又定義了一個AspectInfo協議。AspectInfo protocol是咱們block語法裏面的第一個參數。

instance方法返回當前被hook的實例。originalInvocation方法返回被hooked方法的原始的invocation。arguments方法返回全部方法的參數。它的實現是懶加載。

頭文件中還特地給了一段註釋來講明Aspects的用法和注意點,值得咱們關注。

/** Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second. Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe. */複製代碼

Aspects利用的OC的消息轉發機制,hook消息。這樣會有一些性能開銷。不要把Aspects加到常常被使用的方法裏面。Aspects是用來設計給view/controller 代碼使用的,而不是用來hook每秒調用1000次的方法的。

添加Aspects以後,會返回一個隱式的token,這個token會被用來註銷hook方法的。全部的調用都是線程安全的。

關於線程安全,下面會詳細分析。如今至少咱們知道Aspects不該該被用在for循環這些方法裏面,會形成很大的性能損耗。

@interface NSObject (Aspects)

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

@end複製代碼

Aspects整個庫裏面就只有這兩個方法。這裏能夠看到,Aspects是NSobject的一個extension,只要是NSObject,均可以使用這兩個方法。這兩個方法名字都是同一個,入參和返回值也同樣,惟一不一樣的是一個是加號方法一個是減號方法。一個是用來hook類方法,一個是用來hook實例方法。

方法裏面有4個入參。第一個selector是要給它增長切面的原方法。第二個參數是AspectOptions類型,是表明這個切片增長在原方法的before / instead / after。第4個參數是返回的錯誤。

重點的就是第三個入參block。這個block複製了正在被hook的方法的簽名signature類型。block遵循AspectInfo協議。咱們甚至可使用一個空的block。AspectInfo協議裏面的參數是可選的,主要是用來匹配block簽名的。

返回值是一個token,能夠被用來註銷這個Aspects。

注意,Aspects是不支持hook 靜態static方法的

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

extern NSString *const AspectErrorDomain;複製代碼

這裏定義了錯誤碼的類型。出錯的時候方便咱們調試。

2.Aspects.m
#import "Aspects.h"
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
#import <objc/message.h>複製代碼

#import 導入這個頭文件是爲了下面用到的自旋鎖。#import 和 #import 是使用Runtime的必備頭文件。

typedef NS_OPTIONS(int, AspectBlockFlags) {
      AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
      AspectBlockFlagsHasSignature          = (1 << 30)
};複製代碼

定義了AspectBlockFlags,這是一個flag,用來標記兩種狀況,是否須要Copy和Dispose的Helpers,是否須要方法簽名Signature 。

在Aspects中定義的4個類,分別是AspectInfo,AspectIdentifier,AspectsContainer,AspectTracker。接下來就分別看看這4個類是怎麼定義的。

3. AspectInfo
@interface AspectInfo : NSObject <AspectInfo>
- (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複製代碼

AspectInfo對應的實現

#pragma mark - AspectInfo

@implementation AspectInfo

@synthesize arguments = _arguments;

- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
    NSCParameterAssert(instance);
    NSCParameterAssert(invocation);
    if (self = [super init]) {
        _instance = instance;
        _originalInvocation = invocation;
    }
    return self;
}

- (NSArray *)arguments {
    // Lazily evaluate arguments, boxing is expensive.
    if (!_arguments) {
        _arguments = self.originalInvocation.aspects_arguments;
    }
    return _arguments;
}複製代碼

AspectInfo是繼承於NSObject,而且遵循了AspectInfo協議。在其 - (id)initWithInstance: invocation:方法中,把外面傳進來的實例instance,和原始的invocation保存到AspectInfo類對應的成員變量中。- (NSArray *)arguments方法是一個懶加載,返回的是原始的invocation裏面的aspects參數數組。

aspects_arguments這個getter方法是怎麼實現的呢?做者是經過一個爲NSInvocation添加一個分類來實現的。

@interface NSInvocation (Aspects)
- (NSArray *)aspects_arguments;
@end複製代碼

爲原始的NSInvocation類添加一個Aspects分類,這個分類中只增長一個方法,aspects_arguments,返回值是一個數組,數組裏麪包含了當前invocation的全部參數。

對應的實現

#pragma mark - NSInvocation (Aspects)

@implementation NSInvocation (Aspects)

- (NSArray *)aspects_arguments {
 NSMutableArray *argumentsArray = [NSMutableArray array];
 for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
  [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null];
 }
 return [argumentsArray copy];
}

@end複製代碼

- (NSArray *)aspects_arguments實現很簡單,就是一層for循環,把methodSignature方法簽名裏面的參數,都加入到數組裏,最後把數組返回。

關於獲取方法全部參數的這個- (NSArray *)aspects_arguments方法的實現,有2個地方須要詳細說明。一是爲何循環從2開始,二是[self aspect_argumentAtIndex:idx]內部是怎麼實現的。

先來講說爲啥循環從2開始。

Type Encodings做爲對Runtime的補充,編譯器將每一個方法的返回值和參數類型編碼爲一個字符串,並將其與方法的selector關聯在一塊兒。這種編碼方案在其它狀況下也是很是有用的,所以咱們可使用@encode編譯器指令來獲取它。當給定一個類型時,@encode返回這個類型的字符串編碼。這些類型能夠是諸如int、指針這樣的基本類型,也能夠是結構體、類等類型。事實上,任何能夠做爲sizeof()操做參數的類型均可以用於@encode()。

在Objective-C Runtime Programming Guide中的Type Encoding一節中,列出了Objective-C中全部的類型編碼。須要注意的是這些類型不少是與咱們用於存檔和分發的編碼類型是相同的。但有一些不能在存檔時使用。

注:Objective-C不支持long double類型。@encode(long double)返回d,與double是同樣的。

OC爲支持消息的轉發和動態調用,Objective-C Method 的 Type 信息以 「返回值 Type + 參數 Types」 的形式組合編碼,還須要考慮到 self
和 _cmd 這兩個隱含參數:

- (void)tap; => "v@:"
- (int)tapWithView:(double)pointx; => "i@:d"複製代碼

按照上面的表,咱們能夠知道,編碼出來的字符串,前3位分別是返回值Type,self隱含參數Type @,_cmd隱含參數Type :。

因此從第3位開始,是入參。

假設咱們以- (void)tapView:(UIView *)view atIndex:(NSInteger)index爲例,打印一下methodSignature

(lldb) po self.methodSignature
<NSMethodSignature: 0x60800007df00>
number of arguments = 4
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}
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}
argument 2: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 3: -------- -------- -------- --------
type encoding (q) 'q'
flags {isSigned}
modifiers {}
frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}複製代碼

number of arguments = 4,由於有2個隱含參數self和_cmd,加上入參view和index。

argument return value 0 1 2 3
methodSignature v @ : @ q

第一個argument的frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}memory {offset = 0, size = 0},返回值在這裏不佔size。第二個argument是self,frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}memory {offset = 0, size = 8}。因爲size = 8,下一個frame的offset就是8,以後是16,以此類推。

至於爲什麼這裏要傳遞2,還跟aspect_argumentAtIndex具體實現有關係。

再來看看aspect_argumentAtIndex的具體實現。這個方法還要感謝ReactiveCocoa團隊,爲獲取方法簽名的參數提供了一種優雅的實現方式。

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

getArgumentTypeAtIndex:這個方法是用來獲取到methodSignature方法簽名指定index的type encoding的字符串。這個方法傳出來的字符串直接就是咱們傳進去的index值。好比咱們傳進去的是2,其實傳出來的字符串是methodSignature對應的字符串的第3位。

因爲第0位是函數返回值return value對應的type encoding,因此傳進來的2,對應的是argument2。因此咱們這裏傳遞index = 2進來,就是過濾掉了前3個type encoding的字符串,從argument2開始比較。這就是爲什麼循環從2開始的緣由。

_C_CONST是一個常量,用來判斷encoding的字符串是否是CONST常量。

#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'複製代碼

這裏的Type和OC的Type 是徹底同樣的,只不過這裏是一個C的char類型。

#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)複製代碼

WRAP_AND_RETURN是一個宏定義。這個宏定義裏面調用的getArgument:atIndex:方法是用來在NSInvocation中根據index獲得對應的Argument,最後return的時候把val包裝成對象,返回出去。

在下面大段的if - else判斷中,有不少字符串比較的函數strcmp。

好比說strcmp(argType, @encode(id)) == 0,argType是一個char,內容是methodSignature取出來對應的type encoding,和@encode(id)是同樣的type encoding。經過strcmp比較以後,若是是0,表明類型是相同的。

下面的大段的判斷就是把入參都返回的過程,依次判斷了id,class,SEL,接着是一大推基本類型,char,int,short,long,long long,unsigned char,unsigned int,unsigned short,unsigned long,unsigned long long,float,double,BOOL,bool,char *這些基本類型都會利用WRAP_AND_RETURN打包成對象返回。最後判斷block和struct結構體,也會返回對應的對象。

這樣入參就都返回到數組裏面被接收了。假設仍是上面- (void)tapView:(UIView *)view atIndex:(NSInteger)index爲例子,執行完aspects_arguments,數組裏面裝的的是:

(
  <UIView: 0x7fa2e2504190; frame = (0 80; 414 40); layer = <CALayer: 0x6080000347c0>>",
  1
)複製代碼

總結,AspectInfo裏面主要是 NSInvocation 信息。將NSInvocation包裝一層,好比參數信息等。

4. 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複製代碼

對應實現

#pragma mark - AspectIdentifier

@implementation AspectIdentifier

+ (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;
    }

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

- (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.
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }

 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))) {
            AspectLogError(@"Failed to allocate memory for block invocation.");
   return NO;
  }

  [originalInvocation getArgument:argBuf atIndex:idx];
  [blockInvocation setArgument:argBuf atIndex:idx];
    }

    [blockInvocation invokeWithTarget:self.block];

    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];
}

- (BOOL)remove {
    return aspect_remove(self, NULL);
}

@end複製代碼

在instancetype方法中調用了aspect_blockMethodSignature方法。

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

這個aspect_blockMethodSignature的目的是把傳遞進來的AspectBlock轉換成NSMethodSignature的方法簽名。

AspectBlock的結構以下

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

這裏定義了一個Aspects內部使用的block類型。對系統的Block很熟悉的同窗一眼就會感受二者很像。不熟悉的能夠看看我以前分析Block的文章。文章裏,用Clang把Block轉換成結構體,結構和這裏定義的block很類似。

瞭解了AspectBlock的結構以後,再看aspect_blockMethodSignature函數就比較清楚了。

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

AspectBlockRef layout = (__bridge void *)block,因爲二者block實現相似,因此這裏先把入參block強制轉換成AspectBlockRef類型,而後判斷是否有AspectBlockFlagsHasSignature的標誌位,若是沒有,報不包含方法簽名的error。

注意,傳入的block是全局類型的

(__NSGlobalBlock) __NSGlobalBlock = {
    NSBlock = {
      NSObject = {
        isa = __NSGlobalBlock__
      }
    }
  }複製代碼
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;
    }複製代碼

desc就是原來block裏面對應的descriptor指針。descriptor指針往下偏移2個unsigned long int的位置就指向了copy函數的地址,若是包含Copy和Dispose函數,那麼繼續往下偏移2個(void )的大小。這時指針確定移動到了const char signature的位置。若是desc不存在,那麼也會報錯,該block不包含方法簽名。

const char *signature = (*(const char **)desc);
 return [NSMethodSignature signatureWithObjCTypes:signature];複製代碼

到了這裏,就保證有方法簽名,且存在。最後調用NSMethodSignature的signatureWithObjCTypes方法,返回方法簽名。

舉例說明aspect_blockMethodSignature最終生成的方法簽名是什麼樣子的。

[UIView aspect_hookSelector:@selector(UIView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspects, UIView *view, NSInteger index)
     {

         NSLog(@"按鈕點擊了 %ld",index);

     } error:nil];複製代碼

const char *signature最終得到的字符串是這樣

(const char *) signature = 0x0000000102f72676 "v32@?0@\"<AspectInfo>\"8@\"UIView\"16q24"複製代碼

v32@?0@" "8@"UIView"16q24是Block

^(id<AspectInfo> aspects, UIView *view, NSInteger index){

}複製代碼

對應的Type。void返回值的Type是v,32是offset,@?是block對應的Type,@「 」是第一個參數,@"UIView"是第二個參數,NSInteger對應的Type就是q了。

每一個Type後面跟的數字都是它們各自對應的offset。把最終轉換好的NSMethodSignature打印出來。

<NSMethodSignature: 0x600000263dc0>
      number of arguments = 4
      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 (@) '@"<AspectInfo>"'
          flags {isObject}
          modifiers {}
          frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
          memory {offset = 0, size = 8}
              conforms to protocol 'AspectInfo'
      argument 2: -------- -------- -------- --------
          type encoding (@) '@"UIView"'
          flags {isObject}
          modifiers {}
          frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}
          memory {offset = 0, size = 8}
              class 'DLMenuView'
      argument 3: -------- -------- -------- --------
          type encoding (q) 'q'
          flags {isSigned}
          modifiers {}
          frame {offset = 24, offset adjust = 0, size = 8, size adjust = 0}
          memory {offset = 0, size = 8}複製代碼

回到AspectIdentifier中繼續看instancetype方法,獲取到了傳入的block的方法簽名以後,又調用了aspect_isCompatibleBlockSignature方法。

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:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }
    return YES;
}複製代碼

這個函數的做用是把咱們要替換的方法block和要替換的原方法,進行對比。如何對比呢?對比二者的方法簽名。

入參selector是原方法。

if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
        signaturesMatch = NO;
    }else {
        if (blockSignature.numberOfArguments > 1) {
            const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
            if (blockType[0] != '@') {
                signaturesMatch = NO;
            }
        }複製代碼

先比較方法簽名的參數個數是否相等,不等確定是不匹配,signaturesMatch = NO。若是參數個數相等,再比較咱們要替換的方法裏面第一個參數是否是_cmd,對應的Type就是@,若是不是,也是不匹配,因此signaturesMatch = NO。若是上面兩條都知足,signaturesMatch = YES,那麼就進入下面更加嚴格的對比。

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

這裏循環也是從2開始的。舉個例子來講明爲何從第二位開始比較。仍是用以前的例子。

[UIView aspect_hookSelector:@selector(UIView:atIndex:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspects, UIView *view, NSInteger index)
 {

     NSLog(@"按鈕點擊了 %ld",index);

 } error:nil];複製代碼

這裏我要替換的原方法是UIView:atIndex:,那麼對應的Type是v@:@q。根據上面的分析,這裏的blockSignature是以前調用轉換出來的Type,應該是v@?@" "@"UIView"q。

argument return value 0 1 2 3
methodSignature v @ : @ q
blockSignature v @? @" " @"UIView" q

methodSignature 和 blockSignature 的return value都是void,因此對應的都是v。methodSignature的argument 0 是隱含參數 self,因此對應的是@。blockSignature的argument 0 是block,因此對應的是@?。methodSignature的argument 1 是隱含參數 _cmd,因此對應的是:。blockSignature的argument 1 是 ,因此對應的是@" "。從argument 2開始纔是方法簽名後面的對應可能出現差別,須要比較的參數列表。

最後

if (!signaturesMatch) {
        NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
        AspectError(AspectErrorIncompatibleBlockSignature, description);
        return NO;
    }複製代碼

若是通過上面的比較signaturesMatch都爲NO,那麼就拋出error,Block沒法匹配方法簽名。

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

若是這裏匹配成功了,就會blockSignature所有都賦值給AspectIdentifier。這也就是爲什麼AspectIdentifier裏面有一個單獨的屬性NSMethodSignature的緣由。

AspectIdentifier還有另一個方法invokeWithInfo。

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

註釋也寫清楚了,這個判斷是強迫症患者寫的,到了這裏block裏面的參數是不會大於原始方法的方法簽名裏面參數的個數的。

// The `self` of the block will be the AspectInfo. Optional.
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }複製代碼

把AspectInfo存入到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))) {
            AspectLogError(@"Failed to allocate memory for block invocation.");
            return NO;
          }

        [originalInvocation getArgument:argBuf atIndex:idx];
        [blockInvocation setArgument:argBuf atIndex:idx];
   }

    [blockInvocation invokeWithTarget:self.block];複製代碼

這一段是循環把originalInvocation中取出參數,賦值到argBuf中,而後再賦值到blockInvocation裏面。循環從2開始的緣由上面已經說過了,這裏再也不贅述。最後把self.block賦值給blockInvocation的Target。

總結,AspectIdentifier是一個切片Aspect的具體內容。裏面會包含了單個的 Aspect 的具體信息,包括執行時機,要執行 block 所須要用到的具體信息:包括方法簽名、參數等等。初始化AspectIdentifier的過程實質是把咱們傳入的block打包成AspectIdentifier。

5. 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複製代碼

對應實現

#pragma mark - AspectsContainer

@implementation AspectsContainer

- (BOOL)hasAspects {
    return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;
}

- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
    NSParameterAssert(aspect);
    NSUInteger position = options&AspectPositionFilter;
    switch (position) {
        case AspectPositionBefore:  self.beforeAspects  = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
        case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
        case AspectPositionAfter:   self.afterAspects   = [(self.afterAspects  ?:@[]) arrayByAddingObject:aspect]; break;
    }
}

- (BOOL)removeAspect:(id)aspect {
    for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
                                        NSStringFromSelector(@selector(insteadAspects)),
                                        NSStringFromSelector(@selector(afterAspects))]) {
        NSArray *array = [self valueForKey:aspectArrayName];
        NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
        if (array && index != NSNotFound) {
            NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
            [newArray removeObjectAtIndex:index];
            [self setValue:newArray forKey:aspectArrayName];
            return YES;
        }
    }
    return NO;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects];
}

@end複製代碼

AspectsContainer比較好理解。addAspect會按照切面的時機分別把切片Aspects放到對應的數組裏面。removeAspects會循環移除全部的Aspects。hasAspects判斷是否有Aspects。

AspectsContainer是一個對象或者類的全部的 Aspects 的容器。全部會有兩種容器。

值得咱們注意的是這裏數組是經過Atomic修飾的。關於Atomic須要注意在默認狀況下,由編譯器所合成的方法會經過鎖定機制確保其原子性(Atomicity)。若是屬性具有nonatomic特質,則不須要同步鎖。

6. AspectTracker

@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end複製代碼

對應實現

@implementation AspectTracker

- (id)initWithTrackedClass:(Class)trackedClass {
    if (self = [super init]) {
        _trackedClass = trackedClass;
        _selectorNames = [NSMutableSet new];
        _selectorNamesToSubclassTrackers = [NSMutableDictionary new];
    }
    return self;
}

- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
    return self.selectorNamesToSubclassTrackers[selectorName] != nil;
}

- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
    if (!trackerSet) {
        trackerSet = [NSMutableSet new];
        self.selectorNamesToSubclassTrackers[selectorName] = trackerSet;
    }
    [trackerSet addObject:subclassTracker];
}
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
    NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
    [trackerSet removeObject:subclassTracker];
    if (trackerSet.count == 0) {
        [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName];
    }
}
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName {
    NSMutableSet *hookingSubclassTrackers = [NSMutableSet new];
    for (AspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) {
        if ([tracker.selectorNames containsObject:selectorName]) {
            [hookingSubclassTrackers addObject:tracker];
        }
        [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]];
    }
    return hookingSubclassTrackers;
}
- (NSString *)trackedClassName {
    return NSStringFromClass(self.trackedClass);
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];
}

@end複製代碼

AspectTracker這個類是用來跟蹤要被hook的類。trackedClass是被追蹤的類。trackedClassName是被追蹤類的類名。selectorNames是一個NSMutableSet,這裏會記錄要被hook替換的方法名,用NSMutableSet是爲了防止重複替換方法。selectorNamesToSubclassTrackers是一個字典,key是hookingSelectorName,value是裝滿AspectTracker的NSMutableSet。

addSubclassTracker方法是把AspectTracker加入到對應selectorName的集合中。removeSubclassTracker方法是把AspectTracker從對應的selectorName的集合中移除。subclassTrackersHookingSelectorName方法是一個並查集,傳入一個selectorName,經過遞歸查找,找到全部包含這個selectorName的set,最後把這些set合併在一塊兒做爲返回值返回。

四. Aspects hook前的準備工做

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

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

兩個方法的實現都是調用同一個方法aspect_add,只是傳入的參數不一樣罷了。因此咱們只要從aspect_add開始研究便可。

- aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error
└── aspect_add(self, selector, options, block, error);
    └── aspect_performLocked
        ├── aspect_isSelectorAllowedAndTrack
        └── aspect_prepareClassAndHookSelector複製代碼

這是函數調用棧。從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(^{
        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;
}複製代碼

aspect_add函數一共5個入參,第一個參數是self,selector是外面傳進來須要hook的SEL,options是切片的時間,block是切片的執行方法,最後的error是錯誤。

aspect_performLocked是一個自旋鎖。自旋鎖是效率比較高的一種鎖,相比@synchronized來講效率高得多。

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

若是對iOS中8大鎖不瞭解的,能夠看如下兩篇文章

iOS 常見知識點(三):Lock
深刻理解 iOS 開發中的鎖

可是自旋鎖也是有可能出現問題的:
若是一個低優先級的線程得到鎖並訪問共享資源,這時一個高優先級的線程也嘗試得到這個鎖,它會處於 spin lock 的忙等(busy-wait)狀態從而佔用大量 CPU。此時低優先級線程沒法與高優先級線程爭奪 CPU 時間,從而致使任務遲遲完不成、沒法釋放 lock。再也不安全的 OSSpinLock

OSSpinLock的問題在於,若是訪問這個所的線程不是同一優先級的話,會有死鎖的潛在風險。

這裏暫時認爲是相同優先級的線程,因此OSSpinLock保證了線程安全。也就是說aspect_performLocked是保護了block的線程安全。

如今就剩下aspect_isSelectorAllowedAndTrack函數和aspect_prepareClassAndHookSelector函數了。

接下來先看看aspect_isSelectorAllowedAndTrack函數實現過程。

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

先定義了一個NSSet,這裏面是一個「黑名單」,是不容許hook的函數名。retain, release, autorelease, forwardInvocation:是不容許被hook的。

NSString *selectorName = NSStringFromSelector(selector);
    if ([disallowedSelectorList containsObject:selectorName]) {
        NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
        AspectError(AspectErrorSelectorBlacklisted, errorDescription);
        return NO;
    }複製代碼

當檢測到selector的函數名是黑名單裏面的函數名,當即報錯。

AspectOptions position = options&AspectPositionFilter;
    if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
        NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
        AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
        return NO;
    }複製代碼

再次檢查若是要切片dealloc,切片時間只能在dealloc以前,若是不是AspectPositionBefore,也要報錯。

if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
        NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
        AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
        return NO;
    }複製代碼

當selector不在黑名單裏面了,若是切片是dealloc,且selector在其以前了。這時候就該判斷該方法是否存在。若是self和self.class裏面都找不到該selector,會報錯找不到該方法。

if (class_isMetaClass(object_getClass(self))) {
        Class klass = [self class];
        NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
        Class currentClass = [self class];

        AspectTracker *tracker = swizzledClassesDict[currentClass];
        if ([tracker subclassHasHookedSelectorName:selectorName]) {
            NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
            NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
            NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
            AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
            return NO;
        }複製代碼

class_isMetaClass 先判斷是否是元類。接下來的判斷都是判斷元類裏面可否容許被替換方法。

subclassHasHookedSelectorName會判斷當前tracker的subclass裏面是否包含selectorName。由於一個方法在一個類的層級裏面只能被hook一次。若是已經tracker裏面已經包含了一次,那麼會報錯。

do {
            tracker = swizzledClassesDict[currentClass];
            if ([tracker.selectorNames containsObject:selectorName]) {
                if (klass == currentClass) {
                    // Already modified and topmost!
                    return YES;
                }
                NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
                AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
                return NO;
            }
        } while ((currentClass = class_getSuperclass(currentClass)));複製代碼

在這個do-while循環中,currentClass = class_getSuperclass(currentClass)這個判斷會從currentClass的superclass開始,一直往上找,直到這個類爲根類NSObject。

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判斷和類方法不容許重複替換的檢查後,到此,就能夠把要hook的信息記錄下來,用AspectTracker標記。在標記過程當中,一旦子類被更改,父類也須要跟着一塊兒被標記。do-while的終止條件仍是currentClass = class_getSuperclass(currentClass)。

以上是元類的類方法hook判斷合法性的代碼。

若是不是元類,只要不是hook這"retain", "release", "autorelease", "forwardInvocation:"4種方法,並且hook 「dealloc」方法的時機必須是before,而且selector能被找到,那麼方法就能夠被hook。

經過了selector是否能被hook合法性的檢查以後,就要獲取或者建立AspectsContainer容器了。

// Loads or creates the aspect container.
static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
    NSCParameterAssert(self);
    SEL aliasSelector = aspect_aliasForSelector(selector);
    AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
    if (!aspectContainer) {
        aspectContainer = [AspectsContainer new];
        objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
    }
    return aspectContainer;
}複製代碼

在讀取或者建立AspectsContainer以前,第一步是先標記一下selector。

static SEL aspect_aliasForSelector(SEL selector) {
    NSCParameterAssert(selector);
 return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
}複製代碼

在全局代碼裏面定義了一個常量字符串

static NSString *const AspectsMessagePrefix = @"aspects_";複製代碼

用這個字符串標記全部的selector,都加上前綴"aspects"。而後得到其對應的AssociatedObject關聯對象,若是獲取不到,就建立一個關聯對象。最終獲得selector有"aspects"前綴,對應的aspectContainer。

獲得了aspectContainer以後,就能夠開始準備咱們要hook方法的一些信息。這些信息都裝在AspectIdentifier中,因此咱們須要新建一個AspectIdentifier。

調用AspectIdentifier的instancetype方法,建立一個新的AspectIdentifier

+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error複製代碼

這個instancetype方法,只有一種狀況會建立失敗,那就是aspect_isCompatibleBlockSignature方法返回NO。返回NO就意味着,咱們要替換的方法block和要替換的原方法,二者的方法簽名是不相符的。(這個函數在上面詳解過了,這裏再也不贅述)。方法簽名匹配成功以後,就會建立好一個AspectIdentifier。

[aspectContainer addAspect:identifier withOptions:options];複製代碼

aspectContainer容器會把它加入到容器中。完成了容器和AspectIdentifier初始化以後,就能夠開始準備進行hook了。經過options選項分別添加到容器中的beforeAspects,insteadAspects,afterAspects這三個數組

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

小結一下,aspect_add幹了一些什麼準備工做:

  1. 首先調用aspect_performLocked ,利用自旋鎖,保證整個操做的線程安全
  2. 接着調用aspect_isSelectorAllowedAndTrack對傳進來的參數進行強校驗,保證參數合法性。
  3. 接着建立AspectsContainer容器,利用AssociatedObject關聯對象動態添加到NSObject分類中做爲屬性的。
  4. 再由入參selector,option,建立AspectIdentifier實例。AspectIdentifier主要包含了單個的 Aspect的具體信息,包括執行時機,要執行block 所須要用到的具體信息。
  5. 再將單個的 AspectIdentifier 的具體信息加到屬性AspectsContainer容器中。經過options選項分別添加到容器中的beforeAspects,insteadAspects,afterAspects這三個數組。
  6. 最後調用prepareClassAndHookSelector準備hook。

因爲全篇文章太長,喘口氣,拆成2篇,下部分見下篇。

相關文章
相關標籤/搜索