iOS 實(shí)現(xiàn)AOP編程(Objective-C)

iOS 實(shí)現(xiàn)AOP編程(Objective-C)

一陪腌、AOP與OOP

  • OOP(Object Oriented Programming史煎,面向?qū)ο缶幊蹋?/strong>

OOP比較經(jīng)典的程序設(shè)計(jì)思想阔籽,面向?qū)ο蟮奶攸c(diǎn)是封裝、多態(tài)和繼承纲缓。面向?qū)ο笤O(shè)計(jì)時(shí),每個(gè)對(duì)象職責(zé)不同喊废,封裝的功能也不同祝高。這樣就進(jìn)行了解耦,增加了代碼的重用性污筷、靈活性和擴(kuò)展性褂策。

但這種方式也存在一個(gè)問題,比如颓屑,我們?cè)趦蓚€(gè)類中,可能都需要在每個(gè)方法中進(jìn)行日志記錄(功能完全一樣)耿焊。按OOP 方式揪惦,需要兩個(gè)類的方法中都加入日志功能。這樣就會(huì)有很多重復(fù)代碼罗侯,當(dāng)需要更改日志記錄功能時(shí)器腋,每個(gè)實(shí)現(xiàn)的類都需要更改。

一種解決方法:將日志功能寫在一個(gè)獨(dú)立的類中钩杰,然后再在這兩個(gè)類中調(diào)用該類的日志記錄功能纫塌。修改日志功能只需要修改單獨(dú)的類即可。但是各個(gè)類與獨(dú)立類有耦合讲弄,當(dāng)有一個(gè)類需要增加或移除日志記錄功能時(shí)措左,需要修改該類。另一種方法就是 AOP避除。

  • AOP(Aspect Oriented Program怎披,面向切面編程)

AOP 思想是一種在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)。一般通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)瓶摆。

一般而言凉逛,我們管切入到指定類指定方法的代碼片段稱為切面,而切入到哪些類群井、哪些方法則叫切入點(diǎn)状飞。

AOP 與 OOP 配合,可以很好的分離應(yīng)用的業(yè)務(wù)邏輯與系統(tǒng)級(jí)服務(wù)书斜。有了AOP诬辈,我們就可以把幾個(gè)類共有的代碼,抽取到一個(gè)切片中菩佑,等到需要時(shí)再切入對(duì)象中去自晰,從而改變其原有的行為。

二稍坯、 iOS實(shí)現(xiàn) AOP

實(shí)現(xiàn) AOP 需要語(yǔ)言支持對(duì)對(duì)象的動(dòng)態(tài)擴(kuò)展酬荞,正好 Objective-C的 Runtime 特性可以實(shí)現(xiàn)〈杲伲現(xiàn)在有兩種實(shí)現(xiàn)方式:

  • 1. Method Swizzling
  • 2. 消息轉(zhuǎn)發(fā)

1. Method Swizzling 實(shí)現(xiàn) AOP

在Objective-C中調(diào)用一個(gè)方法,其實(shí)是向一個(gè)對(duì)象發(fā)送消息混巧,查找消息的唯一依據(jù)是selector的名字枪向。

利用Objective-C的動(dòng)態(tài)特性,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對(duì)應(yīng)的方法實(shí)現(xiàn)咧党。

每個(gè)類都有一個(gè)方法列表秘蛔,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系。IMP有點(diǎn)類似函數(shù)指針傍衡,指向具體的Method實(shí)現(xiàn)深员。

  • 每個(gè)類(Class)維護(hù)一張調(diào)度表(dispatch table)用于解析運(yùn)行時(shí)發(fā)送的消息;
  • 調(diào)度表中的每個(gè)實(shí)體(entry)都是一個(gè)方法(Method)蛙埂,其中key值是一個(gè)唯一的名字——選擇器(SEL)倦畅,它對(duì)應(yīng)到一個(gè)實(shí)現(xiàn)(IMP - 實(shí)際上就是指向標(biāo)準(zhǔn)C函數(shù)的指針)。

Method Swizzling就是改變類中SEL 的具體實(shí)現(xiàn)函數(shù)IMP绣的。

struct objc_method {
    SEL method_name             OBJC2_UNAVAILABLE; // selector 名字
    char *method_types          OBJC2_UNAVAILABLE;
    IMP method_imp              OBJC2_UNAVAILABLE; // IMP 實(shí)現(xiàn)方法叠赐,運(yùn)行時(shí)可更改
}  

// 常用函數(shù)
- method_exchangeImplementations  // 交換2個(gè)方法中的IMP

- class_replaceMethod // 會(huì)調(diào)用class_addMethod和method_setImplementation,先實(shí)現(xiàn)方法屡江,再設(shè)置IMP

- method_setImplementation // 直接設(shè)置某個(gè)方法的IMP

可參考EffectiveObjective-C2.0 筆記 - 第二部分

示例 - 日志打印

  • 封裝的 Swizzling 方法
+ (void)swizzClass:(Class)classItem originSel:(SEL)originSel newSel:(SEL)newSel {
    Method orgMd = class_getInstanceMethod(classItem, originSel);
    Method newMd = class_getInstanceMethod(classItem, newSel);

    IMP newImp = method_getImplementation(newMd);

    // 檢查源方法有沒有實(shí)現(xiàn)
    // 如果是YES,表示originSel沒有實(shí)現(xiàn)芭概,則需要先實(shí)現(xiàn),然后再設(shè)置Imp
    // 如果是NO,表示originSel已經(jīng)有存在的實(shí)現(xiàn)方法惩嘉,此時(shí)罢洲,只需要將orgMd和newMd互換就好
    BOOL isAddMdSuccess = class_addMethod(classItem, originSel, newImp, method_getTypeEncoding(newMd));

    if (isAddMdSuccess) {
        // 會(huì)調(diào)用class_addMethod和method_setImplementation,先實(shí)現(xiàn)方法宏怔,再設(shè)置IMP
        class_replaceMethod(classItem, originSel, newImp, method_getTypeEncoding(newMd));
    }
    else {
        // orgMd和newMd互換
        method_exchangeImplementations(orgMd, newMd);
    }
}
  • 注意classItem奏路,看你是替換類的方法,還是實(shí)例對(duì)象的放
+ (Class)getClassItem {
    Class classItem = nil;

    //要特別注意你替換的方法到底是哪個(gè)性質(zhì)的方法
    // When swizzling a Instance method, use the following:
    // 僅替換本實(shí)例方法臊诊,子類方法不變
    classItem = [self class];

    // When swizzling a class method, use the following:
    // 替換類方法
    classItem = object_getClass((id) self);

    return classItem;
}
  • 在 load 中交換
// load 中執(zhí)行 Swizzling
+ (void)load {
    static dispatch_once_t onceToken;

    // dealloc是關(guān)鍵字鸽粉,不能使用@selector(dealloc)
    SEL orgSel = NSSelectorFromString(@"dealloc");

    SEL newSel = @selector(swizzing_dealloc);

    // 保證僅執(zhí)行一次
    dispatch_once(&onceToken, ^{
        [self swizzClass:[self class]
               originSel:orgSel
                  newSel:newSel];
    });
}

- (void)swizzing_dealloc {
    NSLog(@" ** %@ 釋放了 %s", NSStringFromClass([self class]), __func__);

    // 交換后,就不能用 [self dealloc]
    [self swizzing_dealloc];
}
  • 為什么在 load 中交換

+(void)load 方法只要類所在文件被引用就會(huì)被調(diào)用抓艳,在程序運(yùn)行后立即執(zhí)行(在main()之前執(zhí)行)触机,這樣就可以在執(zhí)行方法前,完成方法的替換玷或。

另外 +(void)initialize 方是在類或者其子類的第一個(gè)方法被調(diào)用前調(diào)用儡首,因?yàn)閙ethod swizzling會(huì)影響全局,+load能夠保證在類初始化的時(shí)候就會(huì)被加載偏友,這為改變系統(tǒng)行為提供了一些統(tǒng)一性蔬胯。 但+initialize并不能保證在什么時(shí)候被調(diào)用——事實(shí)上也有可能永遠(yuǎn)也不會(huì)被調(diào)用,例如應(yīng)用程序從未直接的給該類發(fā)送消息位他。

使用注意點(diǎn):

  1. Method Swizzling 需要在 + (void)load{}中使用

  2. Method Swizzling 需要保證只執(zhí)行一次氛濒。 需要使用 dispatch_once;

  3. 注意Class的選擇产场,類對(duì)象還是實(shí)例對(duì)象

  4. Method Swizzling 是以替換 IMP 來(lái)實(shí)現(xiàn)動(dòng)態(tài)修改代碼,這樣實(shí)現(xiàn)的 AOP 不優(yōu)雅舞竿,使用消息轉(zhuǎn)發(fā)可以更優(yōu)雅京景。

2. 消息轉(zhuǎn)發(fā) 實(shí)現(xiàn) AOP

Aspects是一個(gè)已經(jīng)實(shí)現(xiàn)的 AOP 輪子。下面結(jié)合Aspects對(duì)消息轉(zhuǎn)發(fā)的實(shí)現(xiàn)進(jìn)行分析骗奖。

2.1 實(shí)例方法的執(zhí)行

Objective-C 中執(zhí)行實(shí)例方式确徙,其實(shí)是給對(duì)象發(fā)送一個(gè)消息(id objc_msgSend ( id self, SEL cmd, ... )),執(zhí)行流程如下:

image
  • 對(duì)象實(shí)例(instance)收到消息(selector 選擇子+參數(shù))
  • 根據(jù)對(duì)象實(shí)例的ISA找到類對(duì)象执桌,在類對(duì)象中找與選擇子名稱相符的方法鄙皇,如果找到,就調(diào)至執(zhí)行代碼
  • 如果找不到仰挣,則根據(jù)類對(duì)象中的super_class指針找到父類的Class對(duì)象育苟。一直找到NSObject的類對(duì)象
  • 如果NSObject也無(wú)法找到這個(gè)選擇子,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制(message forwarding)
  • 如果消息轉(zhuǎn)發(fā)機(jī)制無(wú)法處理椎木,則拋出異常: doesNotRecognizeSelector:

2.2 消息轉(zhuǎn)發(fā)機(jī)制

在Objective C的方法調(diào)用過程中,當(dāng)無(wú)法響應(yīng)一個(gè)selector時(shí)博烂,在拋出異常之前會(huì)先進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制香椎。這里來(lái)詳細(xì)講解消息轉(zhuǎn)發(fā)的過程:

關(guān)于消息轉(zhuǎn)發(fā),官方文檔在這里: Message Forwarding

其他參考二禽篱、 消息轉(zhuǎn)發(fā)

[圖片上傳失敗...(image-5a84f0-1542965274946)]

在觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制即forwardInvocation:之前畜伐,Runtime提供了兩步來(lái)進(jìn)行輕量級(jí)的動(dòng)態(tài)處理這個(gè)selector.

  • 1. 動(dòng)態(tài)方法 resolveInstanceMethod:

Dynamically provides an implementation for a given selector for an instance method.

這個(gè)方法提供了一個(gè)機(jī)會(huì):為當(dāng)前類無(wú)法識(shí)別的SEL動(dòng)態(tài)增加IMP。

比如:可以通過class_addMethod增加 IMP

void dynamicMethodIMP(id self, SEL _cmd) {/*...implementation...*/}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
    if (aSEL == @selector(resolveThisMethodDynamically)) {
        class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSel];
}


// "v@:"表示方法參數(shù)編碼躺率,v表示Void玛界,@表示OC對(duì)象,:表示SEL類型悼吱。

如果resolveInstanceMethod返回NO慎框,則表示無(wú)法在這一步動(dòng)態(tài)的添加方法,則進(jìn)入下一步:

  • 2. 備援接收者 forwardingTargetForSelector:

Returns the object to which unrecognized messages should first be directed.

這個(gè)方法提供了一個(gè)機(jī)會(huì):把這個(gè)SEL轉(zhuǎn)給其他接收者來(lái)處理后添。

比如

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(dynamicSelector) &&
        [self.myObj respondsToSelector:@selector(dynamicSelector)]) {
        return self.myObj;
    }
    else {
        return [super forwardingTargetForSelector:aSelector];
    }
}
  • 3. 消息轉(zhuǎn)發(fā) message forwarding

如果上述兩步都無(wú)法完成這個(gè)SEL的處理笨枯,則進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制,消息轉(zhuǎn)發(fā)機(jī)制有兩個(gè)比較重要的方法:

  • forwardInvocation: 具體的NSInvocaion
  • methodSignatureForSelector: 返回SEL的方法簽名

這里不得不提一下兩個(gè)類:

  • NSMethodSignature 用來(lái)表示方法的參數(shù)簽名信息:返回值遇西,參數(shù)數(shù)量和類型
  • NSInvocaion SEL + 執(zhí)行SEL的Target + 參數(shù)值

通常馅精,拿到NSInvocaion對(duì)象后,我們可選擇的進(jìn)行如下操作

  • 修改執(zhí)行的SEL
  • 修改執(zhí)行的Target
  • 修改傳入的參數(shù)

然后調(diào)用:[invocation invoke]粱檀,來(lái)執(zhí)行這個(gè)消息洲敢。

_objc_msgForward

我們知道,正常情況下SEL背后會(huì)對(duì)一個(gè)IMP茄蚯,在OC中有一個(gè)特殊的IMP就是:_objc_msgForward压彭。當(dāng)執(zhí)行_objc_msgForward時(shí)睦优,會(huì)直接觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制,即forwardInvocation:哮塞。

2.3 Method Swizzling

上一節(jié)已經(jīng)介紹了Method Swizzling刨秆,可以替換SEL 對(duì)應(yīng)的IMP。

2.4 Aspect 實(shí)現(xiàn)

使用Aspect忆畅,可以在一個(gè)OC方法執(zhí)行前/后插入代碼衡未,也可以替換這個(gè)OC方法的實(shí)現(xiàn)。通過作者暴露的2個(gè)接口可以實(shí)現(xiàn)對(duì)實(shí)例和類的 Hook:

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

下面以在ViewControler的viewWillAppear:方法之后插入一段代碼為例家凯,來(lái)講解hook前后的變化

1) 在沒有hook之前缓醋,ViewController的SEL與IMP關(guān)系如下

image

2) 調(diào)用以下aspect來(lái)Hook viewWillAppear:后

[ViewController aspect_hookSelector:@selector(viewWillAppear:)
                            withOptions:AspectPositionAfter
                             usingBlock:^{
                                 NSLog(@"Insert some code after ViewWillAppear");
                             } error:&error];

image
  • 最初的viewWillAppear: 指向了_objc_msgForward
  • 增加了aspects_viewWillAppear:,指向最初的viewWillAppear:的IMP
  • 最初的forwardInvocation:指向了Aspect提供的一個(gè)C方法ASPECTS_ARE_BEING_CALLED
  • 動(dòng)態(tài)增加了aspects_forwardInvocation:,指向最初的forwardInvocation:的IMP

3) hook后,一個(gè)viewWillAppear:的實(shí)際調(diào)用順序:

  • object收到selector(viewWillAppear:)的消息
  • 找到對(duì)應(yīng)的IMP:_objc_msgForward绊诲,執(zhí)行后觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制送粱。
  • object收到forwardInvocation:消息
  • 找到對(duì)應(yīng)的IMP:ASPECTS_ARE_BEING_CALLED,執(zhí)行IMP
    • 向object對(duì)象發(fā)送aspects_viewWillAppear:,執(zhí)行最初的viewWillAppear方法的IMP
    • 執(zhí)行插入的block代碼
    • 如果ViewController無(wú)法響應(yīng)aspects_viewWillAppear掂之,則向object對(duì)象發(fā)送__aspects_forwardInvocation:來(lái)執(zhí)行最初的forwardInvocation IMP

所以抗俄,Aspects是采用了集中式的hook方式,所有的調(diào)用最后走的都是一個(gè)C函數(shù)ASPECTS_ARE_BEING_CALLED世舰。

2.4.1 核心類/數(shù)據(jù)結(jié)構(gòu)
image

1) Aspects 內(nèi)部定義了兩個(gè)協(xié)議:

  • AspectToken

AspectToken 協(xié)議旨在讓使用者可以靈活的注銷之前添加過的 Hook

/// 用于注銷 Hook
@protocol AspectToken /// 注銷一個(gè) aspect.
/// 返回 YES 表示注銷成功动雹,否則返回 NO
- (BOOL)remove;
@end
  • AspectInfo

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

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

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

  • AspectInfo

切面信息:NSInvocation的容器震蒋,表示一個(gè)執(zhí)行的Command茸塞,遵循 AspectInfo 協(xié)議。AspectInfo 扮演了一個(gè)提供 Hook 信息的角色查剖。

@interface AspectInfo : NSObject <AspectInfo>
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end

  • AspectIdentifier

切面 ID:代表一個(gè)Aspect的具體信息钾虐,包括被Hook的對(duì)象,SEL笋庄,插入的block等具體信息禾唁,遵循 AspectToken 協(xié)議。

@interface AspectIdentifier : NSObject
@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
  • AspectContainer

AspectIdentifier的容器:以SEL合成key无切,然后作為關(guān)聯(lián)對(duì)象存儲(chǔ)到對(duì)應(yīng)的類/對(duì)象里荡短。包括beforeAspects,insteadAspects哆键,afterAspects

@interface AspectsContainer : NSObject
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end

  • AspectTracker

切面跟蹤器:跟蹤一個(gè)類的繼承鏈中的hook狀態(tài):包括被hook的類掘托,哪些SEL被hook了。

@interface AspectTracker : NSObject
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
@end

其原理大致為

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

AspectTracker 是從下而上追蹤籍嘹,最底層的 parentEntry 為 nil闪盔,父類的 parentEntry 為子類的 tracker弯院。

3)一個(gè)結(jié)構(gòu)體:

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

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

  • static NSMutableDictionary *swizzledClassesDict;
  • static NSMutableSet *swizzledClasses;
2.4.2 hook過程

1. 對(duì)Class和MetaClass進(jìn)行進(jìn)行合法性檢查泪掀,判斷能否hook听绳,規(guī)則如下

  • retain,release,autorelease,forwoardInvocation:不能被hook
  • dealloc只能在方法前hook
  • 類的繼承關(guān)系中,同一個(gè)方法只能被hook一次

2. 創(chuàng)建AspectsContainer對(duì)象异赫,以aspects_ + SEL為key椅挣,作為關(guān)聯(lián)對(duì)象依附到被hook 的對(duì)象上

objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);

3. 創(chuàng)建AspectIdentifier對(duì)象,并且添加到AspectsContainer對(duì)象里存儲(chǔ)起來(lái)塔拳。這個(gè)過程分為兩步

  • 生成block的方法簽名NSMethodSignature
  • 對(duì)比block的方法簽名和待hook的方法簽名是否兼容(參數(shù)個(gè)數(shù)鼠证,按照順序的類型)

4. 根據(jù)hook實(shí)例對(duì)象/類對(duì)象/類元對(duì)象的方法做不同處理。其中靠抑,對(duì)于上文以類方法來(lái)hook的時(shí)候量九,分為兩步

  • hook類對(duì)象的forwoardInvocation:方法,指向一個(gè)靜態(tài)的C方法颂碧,并且創(chuàng)建一個(gè)aspects_ forwoardInvocation:動(dòng)態(tài)添加到之前的類中
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
    class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
  • hook類對(duì)象的viewWillAppear:方法讓其指向_objc_msgForward,動(dòng)態(tài)添加aspects_viewWillAppear:指向最初的viewWillAppear:實(shí)現(xiàn)
2.4.3 Hook實(shí)例的方法

Aspects支持只hook一個(gè)對(duì)象的實(shí)例方法

只不過在第4步略有出入荠列,當(dāng)hook一個(gè)對(duì)象的實(shí)例方法的時(shí)候:

  • 新建一個(gè)子類,_Aspects_ViewController,并且按照上述的方式hook forwoardInvocation:
  • hook _Aspects_ViewController的class方法载城,讓其返回ViewController
  • hook 子類的類元對(duì)象弯予,讓其返回ViewController
  • 調(diào)用objc_setClass來(lái)修改ViewController的類為_Aspects_ViewController

這樣做,就可以通過object_getClass(self)獲得類名个曙,然后看看是否有前綴類名來(lái)判斷是否被hook過了

2.4.4 其他

1) object_getClass/與self.class的區(qū)別

  • object_getClass獲得的是isa的指向
  • self.class則不一樣,當(dāng)self是實(shí)例對(duì)象的時(shí)候受楼,返回的是類對(duì)象垦搬,否則則返回自身。

比如:

TestClass * testObj = [[TestClass alloc] init];
//Same
logAddress([testObj class]);
logAddress([TestClass class]);

//Not same
logAddress(object_getClass(testObj));
logAddress(object_getClass([TestClass class]));

輸出

2017-05-22 22:41:48.216 OCTest[899:25934] 0x107d10930
2017-05-22 22:41:48.216 OCTest[899:25934] 0x107d10930
2017-05-22 22:41:48.216 OCTest[899:25934] 0x107d10930
2017-05-22 22:41:49.061 OCTest[899:25934] 0x107d10908

2) Block簽名

block因?yàn)楸澈笃鋵?shí)是一個(gè)C結(jié)構(gòu)體艳汽,結(jié)構(gòu)體中存儲(chǔ)著著一個(gè)函數(shù)指針來(lái)指向?qū)嶋H的方法體

Block的內(nèi)存布局如下

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;

對(duì)應(yīng)生成NSMethodSignature的方法:

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

3) 效率

消息轉(zhuǎn)發(fā)機(jī)制相對(duì)于正常的方法調(diào)用來(lái)說(shuō)是比較昂貴的猴贰,所以一定不要用消息轉(zhuǎn)發(fā)機(jī)制來(lái)處理那些一秒鐘成百上千次的調(diào)用。

?著作權(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)店門踱蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窿给,“玉大人贵白,你說(shuō)我怎么就攤上這事”琅荩” “怎么了禁荒?”我有些...
    開封第一講書人閱讀 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)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了屹逛?” 一聲冷哼從身側(cè)響起础废,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(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
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至躯砰,卻和暖如春每币,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琢歇。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(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)容