iOS - 從 Aspects 源碼中我學(xué)到了什么?

序言

AOP (Aspect-oriented programming) 譯為 “面向切面編程”势决,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)阻塑。利用 AOP 可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低果复,提高程序的可重用性陈莽,同時(shí)提高了開發(fā)的效率。

AOP 目前是較為熱門的一個(gè)話題虽抄,盡管你也許沒有聽說過它走搁,但是你的項(xiàng)目中可能已經(jīng)滲入了它,例如:用戶統(tǒng)計(jì)(不添加一行代碼即實(shí)現(xiàn)對(duì)所有 ViewController 的跟蹤日志)迈窟。

Aspects 作為 Objective-C 語言編寫的 AOP 庫(kù)私植,適用于 iOS 和 Mac OS X,使用體驗(yàn)簡(jiǎn)單愉快车酣,已經(jīng)在 GitHub 摘得 7k+ Star曲稼。Aspects 內(nèi)部實(shí)現(xiàn)比較健全,考慮到了 Hook 安全方面可能發(fā)生的種種問題湖员,非常值得我們學(xué)習(xí)躯肌。

Note: 本文內(nèi)引用 Aspects 源碼版本為 v1.4.1,要求讀者具備一定的 Runtime 知識(shí)破衔。

索引

  • AOP 簡(jiǎn)介
  • Aspects 簡(jiǎn)介
  • Aspects 結(jié)構(gòu)剖析
  • Aspects 核心代碼剖析
  • 優(yōu)秀 AOP 庫(kù)應(yīng)該具備的特質(zhì)
  • 總結(jié)
一 AOP簡(jiǎn)介

在運(yùn)行時(shí)清女,動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程晰筛。

AOP (Aspect-oriented programming)嫡丙,即 “面向切面編程” 是一種編程范式,或者說是一種編程思想读第,它解決了 OOP (Object-oriented programming) 的延伸問題曙博。

什么時(shí)候需要使用 AOP

一般做用戶頁面統(tǒng)計(jì)時(shí)需要

思路: Hook UIViewController 的 viewWillAppear: 和 viewWillDisappear: 方法,在原方法執(zhí)行之后記錄需要統(tǒng)計(jì)的信息上報(bào)即可怜瞒。

Note: 簡(jiǎn)單通過 Method Swizzling 來 Hook 不是不可以父泳,但是有很多安全隱患!

二 Aspects簡(jiǎn)介

Aspects 是一個(gè)使用起來簡(jiǎn)單愉快的 AOP 庫(kù)吴汪,使用 Objective-C 編寫惠窄,適用于 iOS 與 Mac OS X。

Aspects 內(nèi)部實(shí)現(xiàn)考慮到了很多 Hook 可能引發(fā)的問題漾橙,筆者在看源碼的過程中摳的比較細(xì)杆融,真的是受益匪淺。

Aspects 簡(jiǎn)單易用霜运,作者通過在 NSObject (Aspects) 分類中暴露出的兩個(gè)接口分別提供了對(duì)實(shí)例和 Class 的 Hook 實(shí)現(xiàn):

@interface NSObject (Aspects)
+ (id)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
- (id)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
@end

Aspects 支持實(shí)例 Hook脾歇,相較其他 Objective-C AOP 庫(kù)而言可操作粒度更小蒋腮,適合的場(chǎng)景更加多樣化。作為使用者無需進(jìn)行更多的操作即可 Hook 指定實(shí)例或者 Class 的指定 SEL藕各,AspectOptions 參數(shù)可以指定 Hook 的點(diǎn)池摧,以及是否執(zhí)行一次之后就撤銷 Hook。

2.1 Aspects 結(jié)構(gòu)剖析
Aspects結(jié)構(gòu)剖析.png

盡管 Aspects 只有不到千行的源碼激况,但是其內(nèi)部實(shí)現(xiàn)考慮到了很多 Hook 相關(guān)的安全問題和其他細(xì)節(jié)作彤,對(duì)比其他 Objective-C AOP 開源項(xiàng)目來說 Aspects 更為健全,所以我自己在扒 Aspects 源碼時(shí)也看的比較仔細(xì)誉碴。

2.2 Aspects 內(nèi)部結(jié)構(gòu)
  1. Aspects 內(nèi)部定義了兩個(gè)協(xié)議:

AspectToken:用于注銷 Hook
AspectInfo: 嵌入 Hook 中的 Block 首位參數(shù)

  1. Aspects 內(nèi)部還定義了 4 個(gè)類:

AspectInfo:切面信息宦棺,遵循 AspectInfo 協(xié)議
AspectIdentifier:切面 ID瓣距,應(yīng)該遵循 AspectToken 協(xié)議(作者漏掉了黔帕,已提 PR)
AspectsContainer:切面容器
AspectTracker:切面跟蹤器

  1. 一個(gè)結(jié)構(gòu)體

AspectBlockRef:即 _AspectBlock,充當(dāng)內(nèi)部 Block

4.兩個(gè)內(nèi)部靜態(tài)全局變量:

static NSMutableDictionary *swizzledClassesDict;
static NSMutableSet *swizzledClasses;

2.3 各個(gè)知識(shí)點(diǎn)詳解
2.3.1 AspectToken

AspectToken 協(xié)議旨在讓使用者可以靈活的注銷之前添加過的 Hook蹈丸,內(nèi)部規(guī)定遵守此協(xié)議的對(duì)象須實(shí)現(xiàn) remove 方法成黄。

/// 不透明的 Aspect Token,用于注銷 Hook
@protocol AspectToken /// 注銷一個(gè) aspect.
/// 返回 YES 表示注銷成功逻杖,否則返回 NO
- (BOOL)remove;
@end
2.3.2 AspectInfo

AspectInfo 協(xié)議旨在規(guī)范對(duì)一個(gè)切面奋岁,即 aspect 的 Hook 內(nèi)部信息的紕漏,我們?cè)?Hook 時(shí)添加切面的 Block 第一個(gè)參數(shù)就遵守此協(xié)議荸百。

/// AspectInfo 協(xié)議是我們塊語法的第一個(gè)參數(shù)闻伶。
@protocol AspectInfo /// 當(dāng)前被 Hook 的實(shí)例
- (id)instance;
/// 被 Hook 方法的原始 invocation
- (NSInvocation *)originalInvocation;
/// 所有方法參數(shù)(裝箱之后的)惰性執(zhí)行
- (NSArray *)arguments;
@end

Note: 裝箱是一個(gè)開銷昂貴操作,所以用到再去執(zhí)行够话。

2.3.3 AspectInfo

Note: AspectInfo 在這里是一個(gè) Class蓝翰,其遵守上文中講到的 AspectInfo 協(xié)議,不要混淆女嘲。

AspectInfo 類定義:

@interface AspectInfo : NSObject - (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

Note: 關(guān)于裝箱畜份,對(duì)于提供一個(gè) NSInvocation 就可以拿到其 arguments 這一點(diǎn)上,ReactiveCocoa 團(tuán)隊(duì)提供了很大貢獻(xiàn)(細(xì)節(jié)見 Aspects 內(nèi)部 NSInvocation 分類)欣尼。

AspectInfo 比較簡(jiǎn)單爆雹,參考 ReactiveCocoa 團(tuán)隊(duì)提供的 NSInvocation 參數(shù)通用方法可將參數(shù)裝箱為 NSValue,簡(jiǎn)單來說 AspectInfo 扮演了一個(gè)提供 Hook 信息的角色愕鼓。

2.3.4 AspectIdentifier

AspectIdentifier 類定義:

@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id)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

Note: AspectIdentifier 實(shí)際上是添加切面的 Block 的第一個(gè)參數(shù)钙态,其應(yīng)該遵循 AspectToken 協(xié)議,事實(shí)上也的確如此菇晃,其提供了 remove 方法的實(shí)現(xiàn)驯绎。

AspectIdentifier 內(nèi)部需要注意的是由于使用 Block 來寫 Hook 中我們加的料,這里生成了 blockSignature谋旦,在 AspectIdentifier 初始化的過程中會(huì)去判斷 blockSignature 與入?yún)?object 的 selector 得到的 methodSignature 的兼容性剩失,兼容性判斷成功才會(huì)順利初始化屈尼。

2.3.5 AspectsContainer

AspectsContainer 類定義:

@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

AspectsContainer 作為切面的容器類,關(guān)聯(lián)指定對(duì)象的指定方法拴孤,內(nèi)部有三個(gè)切面隊(duì)列脾歧,分別容納關(guān)聯(lián)指定對(duì)象的指定方法中相對(duì)應(yīng) AspectOption 的 Hook:

  • NSArray *beforeAspects; - AspectPositionBefore
  • NSArray *insteadAspects; - AspectPositionInstead
  • NSArray *afterAspects; - AspectPositionAfter

為什么要說關(guān)聯(lián)呢?因?yàn)?AspectsContainer 是在 NSObject 分類中通過 AssociatedObject 方法與當(dāng)前要 Hook 的目標(biāo)關(guān)聯(lián)在一起的演熟。

Note: 關(guān)聯(lián)目標(biāo)是 Hook 之后的 Selector鞭执,即 aliasSelector(原始 SEL 名稱加 aspects_ 前綴對(duì)應(yīng)的 SEL)。

2.3.6 AspectTracker

AspectTracker 類定義:

@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, weak) AspectTracker *parentEntry;
@end

AspectTracker 作為切面追蹤器芒粹,原理大致如下:

// Add the selector as being modified.
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
    AspectTracker *tracker = swizzledClassesDict[currentClass];
    if (!tracker) {
        tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
        swizzledClassesDict[(id)currentClass] = tracker;
    }
    [tracker.selectorNames addObject:selectorName];
    // All superclasses get marked as having a subclass that is modified.
    parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));

Note: 全局變量 swizzledClassesDict 中的 value 對(duì)應(yīng)著 AspectTracker 指針兄纺。

就是說 AspectTracker 是從下而上追蹤,最底層的 parentEntry 為 nil化漆,父類的 parentEntry 為子類的 tracker估脆。

2.3.7 AspectBlockRef

AspectBlockRef,即 struct _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;

Note: __unused 宏定義實(shí)際上是 attribute((unused)) GCC 定語疙赠,旨在告訴編譯器“如果我沒有在后面使用到這個(gè)變量也別警告我”。

2.3.8 Aspects 靜態(tài)全局變量
2.3.8.1 static NSMutableDictionary *swizzledClassesDict;

static NSMutableDictionary *swizzledClassesDict; 在 Aspects 中扮演著已混寫類字典的角色

Aspects 內(nèi)部提供了專門訪問這個(gè)全局字典的方法:

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

這個(gè)全局變量可以簡(jiǎn)單理解為記錄整個(gè) Hook 影響的 Class 包含其 SuperClass 的追蹤記錄的全局字典朦拖。

2.3.8.2 static NSMutableSet *swizzledClasses;

static NSMutableSet *swizzledClasses; 在 Aspects 中擔(dān)當(dāng)記錄已混寫類的角色

Aspects 內(nèi)部提供一個(gè)用于修改這個(gè)全局變量?jī)?nèi)容的方法:

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

Note: 注意 @synchronized(swizzledClasses)圃阳。

這個(gè)全局變量記錄了 forwardInvocation: 被混寫的的類名稱。

Note: 注意在用途上與 static NSMutableDictionary *swizzledClassesDict; 區(qū)分理解璧帝。

三 Aspects 核心代碼剖析

Aspects 的整體實(shí)現(xiàn)代碼不超過一千行捍岳,而且考慮的情況也比較全面,非常值得大家花時(shí)間去讀一下睬隶,這里給出部分核心碼的理解锣夹。

3.1 Hook Class && Hook Instance

Aspects 不光支持 Hook Class 還支持 Hook Instance,這提供了更小粒度的控制理疙,配合 Hook 的撤銷功能可以更加靈活精準(zhǔn)的做我們想做的事~

Aspects 為了能區(qū)別 Class 和 Instance 的邏輯晕城,實(shí)現(xiàn)了名為 aspect_hookClass 的方法,我認(rèn)為其中的實(shí)現(xiàn)值得我用一部分篇幅來單獨(dú)講解窖贤,也覺得讀者們有必要花點(diǎn)時(shí)間理解這里的實(shí)現(xiàn)邏輯砖顷。

static Class aspect_hookClass(NSObject *self, NSError **error) {
        // 斷言 self
        NSCParameterAssert(self);
        // class
    Class statedClass = self.class;
    // isa
    Class baseClass = object_getClass(self);
    NSString *className = NSStringFromClass(baseClass);
        // 已經(jīng)子類化過了
    if ([className hasSuffix:AspectsSubclassSuffix]) {
        return baseClass;
                // 我們混寫了一個(gè) class 對(duì)象,而非一個(gè)單獨(dú)的 object
    }else if (class_isMetaClass(baseClass)) {
            // baseClass 是元類赃梧,則 self 是 Class 或 MetaClass滤蝠,混寫 self
                return aspect_swizzleClassInPlace((Class)self);
                // 可能是一個(gè) KVO'ed class∈卩郑混寫就位物咳。也要混寫 meta classes。
            }else if (statedClass != baseClass) {
                    // 當(dāng) .class 和 isa 指向不同的情況蹄皱,混寫 baseClass
                    return aspect_swizzleClassInPlace(baseClass);
                }
        // 默認(rèn)情況下览闰,動(dòng)態(tài)創(chuàng)建子類
        // 拼接子類后綴 AspectsSubclassSuffix
    const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
    // 嘗試用拼接后綴的名稱獲取 isa
    Class subclass = objc_getClass(subclassName);
        // 找不到 isa芯肤,代表還沒有動(dòng)態(tài)創(chuàng)建過這個(gè)子類
    if (subclass == nil) {
            // 創(chuàng)建一個(gè) class pair,baseClass 作為新類的 superClass压鉴,類名為 subclassName
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) { // 返回 nil崖咨,即創(chuàng)建失敗
                        NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
                        AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
                        return nil;
                    }
                // 混寫 forwardInvocation:
        aspect_swizzleForwardInvocation(subclass);
        // subClass.class = statedClass
        aspect_hookedGetClass(subclass, statedClass);
        // subClass.isa.class = statedClass
        aspect_hookedGetClass(object_getClass(subclass), statedClass);
        // 注冊(cè)新類
        objc_registerClassPair(subclass);
    }
        // 覆蓋 isa
    object_setClass(self, subclass);
    return subclass;
}

Note: 其實(shí)這里的難點(diǎn)就在于對(duì) .class 和 object_getClass 的區(qū)分。

  • .class 當(dāng) target 是 Instance 則返回 Class油吭,當(dāng) target 是 Class 則返回自身
  • object_getClass 返回 isa 指針的指向

Note: 動(dòng)態(tài)創(chuàng)建一個(gè) Class 的完整步驟也是我們應(yīng)該注意的击蹲。

  • objc_allocateClassPair
  • class_addMethod
  • class_addIvar
  • objc_registerClassPair
3.2 Hook 的實(shí)現(xiàn)

在上面 aspect_hookClass 方法中,不僅僅是返回一個(gè)要 Hook 的 Class婉宰,期間還做了一些細(xì)節(jié)操作歌豺,不論是 Class 還是 Instance,都會(huì)調(diào)用 aspect_swizzleForwardInvocation 方法心包,下面對(duì)這個(gè)方法剖析

static void aspect_swizzleForwardInvocation(Class klass) {
    // 斷言 klass
    NSCParameterAssert(klass);
    // 如果沒有 method类咧,replace 實(shí)際上會(huì)像是 class_addMethod 一樣
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    // 拿到 originalImplementation 證明是 replace 而不是 add,情況少見
    if (originalImplementation) {
        // 添加 AspectsForwardInvocationSelectorName 的方法谴咸,IMP 為原生 forwardInvocation:
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

上面的方法就是把要 Hook 的目標(biāo) Class 的 forwardInvocation: 混寫了轮听,混寫之后 forwardInvocation: 的具體實(shí)現(xiàn)在 ASPECTS_ARE_BEING_CALLED 中骗露,里面能看到 invoke 標(biāo)識(shí)位的不同是如何實(shí)現(xiàn)的岭佳,還有一些其他的實(shí)現(xiàn)細(xì)節(jié):

// 宏定義,以便于我們有一個(gè)更明晰的 stack trace
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
    [aspect invokeWithInfo:info];\
    if (aspect.options & AspectOptionAutomaticRemoval) { \
        aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
    } \
}
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
    // __unsafe_unretained NSObject *self 不解釋了
    // 斷言 self, invocation
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    // 從 invocation 可以拿到很多東西萧锉,比如 originalSelector
    SEL originalSelector = invocation.selector;
    // originalSelector 加前綴得到 aliasSelector
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
// 用 aliasSelector 替換 invocation.selector
    invocation.selector = aliasSelector;
     
    // Instance 的容器
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
    // Class 的容器
    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 { // 否則正常執(zhí)行
        // 遍歷 invocation.target 及其 superClass 找到實(shí)例可以響應(yīng) aliasSelector 的點(diǎn) invoke
        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);
    // 如果沒有 hook珊随,則執(zhí)行原始實(shí)現(xiàn)(通常會(huì)拋出異常)
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
        // 如果可以響應(yīng) originalForwardInvocationSEL,表示之前是 replace method 而非 add method
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }
    // 移除 aspectsToRemove 隊(duì)列中的 AspectIdentifier柿隙,執(zhí)行 remove
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
#undef aspect_invoke

Note: aspect_invoke 宏定義的作用域叶洞。

  • 代碼實(shí)現(xiàn)對(duì)應(yīng)了 Hook 的 AspectOptions 參數(shù)的 Before,Instead 和 After禀崖。
  • aspect_invoke 中 aspectsToRemove 是一個(gè) NSArray衩辟,里面容納著需要被銷戶的 Hook,即 AspectIdentifier(之后會(huì)調(diào)用 remove 移除)波附。
  • 遍歷invocation.target及其 superClass 找到實(shí)例可以響應(yīng) aliasSelector 的點(diǎn) invoke 實(shí)現(xiàn)代碼艺晴。
3.3 Block Hook

Aspects 讓我們?cè)谥付?Class 或 Instance 的特定 Selector 執(zhí)行時(shí),根據(jù) AspectOptions 插入我們自己的 Block 做 Hook掸屡,而這個(gè) Block 內(nèi)部有我們想要的有關(guān)于當(dāng)前 Target 和 Selector 的信息封寞,我們來看一下 Aspects 是怎么辦到的

- (BOOL)invokeWithInfo:(id)info {
    NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
    NSInvocation *originalInvocation = info.originalInvocation;
    NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
    // 偏執(zhí)。我們已經(jīng)在 hook 注冊(cè)的時(shí)候檢查過了仅财,(不過這里我們還要檢查)狈究。
    if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
        AspectLogError(@"Block has too many arguments. Not calling %@", info);
        return NO;
    }
    // block 的 `self` 將會(huì)是 AspectInfo≌登螅可選的抖锥。
    if (numberOfArguments > 1) {
        [blockInvocation setArgument:&info atIndex:1];
    }
    
    // 簡(jiǎn)歷參數(shù)分配內(nèi)存 argBuf 然后從 originalInvocation 取 argument 賦值給 blockInvocation
    void *argBuf = NULL;
    for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
        const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
        NSUInteger argSize;
        NSGetSizeAndAlignment(type, &argSize, NULL);
        
        // reallocf 優(yōu)點(diǎn)亿眠,如果創(chuàng)建內(nèi)存失敗會(huì)自動(dòng)釋放之前的內(nèi)存,講究
        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];
    }
    
    // 執(zhí)行
    [blockInvocation invokeWithTarget:self.block];
    
    // 釋放 argBuf
    if (argBuf != NULL) {
        free(argBuf);
    }
    return YES;
}

考慮兩個(gè)問題:

  • [blockInvocation setArgument:&info atIndex:1]; 為什么要在索引 1 處插入呢磅废?
  • for (NSUInteger idx = 2; idx < numberOfArguments; idx++) 為什么要從索引 2 開始遍歷參數(shù)呢缕探?
總結(jié)
  • 文章簡(jiǎn)單介紹了 AOP 的概念,希望能給各位讀者對(duì) AOP 思想的理解提供微薄的幫助还蹲。

  • 文章系統(tǒng)的剖析了 Aspects 開源庫(kù)的內(nèi)部結(jié)構(gòu)爹耗,希望能讓大家在瀏覽 Aspects 源碼時(shí)快速定位代碼位置,找到核心內(nèi)容谜喊。

  • 文章重點(diǎn)分析了 Aspects 的核心代碼潭兽,提煉了一些筆者認(rèn)為值得注意的點(diǎn),但愿可以在大家扒源碼時(shí)提供一些指引斗遏。


本文參考 從 Aspects 源碼中我學(xué)到了什么山卦? ,非常感謝該作者诵次。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末账蓉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逾一,更是在濱河造成了極大的恐慌铸本,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遵堵,死亡現(xiàn)場(chǎng)離奇詭異箱玷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陌宿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門锡足,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壳坪,你說我怎么就攤上這事舶得。” “怎么了爽蝴?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵沐批,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我霜瘪,道長(zhǎng)珠插,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任颖对,我火速辦了婚禮捻撑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己顾患,他們只是感情好番捂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著江解,像睡著了一般设预。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上犁河,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天鳖枕,我揣著相機(jī)與錄音,去河邊找鬼桨螺。 笑死宾符,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灭翔。 我是一名探鬼主播魏烫,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肝箱!你這毒婦竟也來了哄褒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤煌张,失蹤者是張志新(化名)和其女友劉穎呐赡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唱矛,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罚舱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年井辜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绎谦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粥脚,死狀恐怖窃肠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刷允,我是刑警寧澤冤留,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站树灶,受9級(jí)特大地震影響纤怒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜天通,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一泊窘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦烘豹、人聲如沸瓜贾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祭芦。三九已至,卻和暖如春憔鬼,著一層夾襖步出監(jiān)牢的瞬間龟劲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工轴或, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咸灿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓侮叮,卻偏偏與公主長(zhǎng)得像避矢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子囊榜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容