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):
Method Swizzling 需要在 + (void)load{}中使用
Method Swizzling 需要保證只執(zhí)行一次氛濒。 需要使用 dispatch_once;
注意Class的選擇产场,類對(duì)象還是實(shí)例對(duì)象
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í)行流程如下:
- 對(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
[圖片上傳失敗...(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)系如下
2) 調(diào)用以下aspect來(lái)Hook viewWillAppear:后
[ViewController aspect_hookSelector:@selector(viewWillAppear:)
withOptions:AspectPositionAfter
usingBlock:^{
NSLog(@"Insert some code after ViewWillAppear");
} error:&error];
- 最初的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)
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)用。