前段時間關注過objc實現的AOP。
在GitHub找到了其中的兩個庫:AOP-in-Objective-C 和 AOP-for-Objective-C
第一個是基於NSProxy來實現的;第二個是基於GCD以及block實現的;
兩者都使用了Cocoa的運行時編程技術,將攔截器注入給代理對象,使其幹涉真是對象的執行順序從而達到給代碼增加“切面”的目的,這裡的模式就是通常的代理模式。
因為時間關係,暫時只看了第一個庫的代碼,下面簡短地分析一下。
NSProxy:如其名,它出現的目的就是為了來“代理”一個真實對象的。這是Foundation庫的內建實現。大部門人都知道NSObject是通常Cocoa中的根類,沒錯,但其實根類並不止一個,NSProxy也是跟NSObject的根類,只是它是個抽象類別並且不是用於通常意義上的編程目的,所以不是那麼廣為人知(事實上我也是今天才知道)。並且NSObject看到它你以為它是個類。但今天看NSProxy定義的時候,我發現它的標頭檔裡是這樣定義的:
@interface NSProxy <NSObject>
開始我很莫名其妙,如果是繼承自NSObject的話,應該是個冒號。這種寫法明顯就是實現協議的寫法啊。於是,查看了一下資料,果然還有個NSObject的協議,並且NSObject類自身也實現了NSObject協議。具體資料請看這篇文章。
NSProxy與NSObject一虛一實,並且它們都實現了NSObject協議。這讓NSProxy的實作類別能夠很好地“代理”NSObject子類,並且把NSObject協議抽象出來,也讓他們能夠共用某些行為。
來看看它是如何工作的(測試代碼見AOPLibTest.m檔案):
在你需要使用AOP的地方,你首先需要執行個體化一個對象,比如你需要執行個體化一個NSMutableArray,你需要使用AOPProxy來執行個體化它:
NSMutableArray* testArray = (NSMutableArray*)[[AOPProxy alloc] initWithNewInstanceOfClass:[NSMutableArray class]];
這裡,其實是間接執行個體化。它提供了一個介面,你把你的類名傳給它,由它給你執行個體化。事實上,這是一種注入方式,而看完這個方法的定義你就會看到其實它返回給你的並不是NSMutableArray的一個執行個體(其實是AOPProxy,而它們之所以能互相強制轉換是因為他們都實現了NSObject協議):
- (id) initWithNewInstanceOfClass:(Class) class { // create a new instance of the specified class id newInstance = [[class alloc] init]; // invoke my designated initializer [self initWithInstance:newInstance]; // release the new instance [newInstance release]; // finally return the configured self return self;}
上面的self指代的就是AOPProxy,其中的initWithInstance方法:
- (id) initWithInstance:(id)anObject { parentObject = [anObject retain]; methodStartInterceptors = [[NSMutableArray alloc] init]; methodEndInterceptors = [[NSMutableArray alloc] init]; return self;}
可以看到,它在內部hold住了真實對象,並且執行個體化了兩個數組,用來儲存方法執行前後的攔截器集合。
下面,我們可以為NSMutableArray增加攔截器了:
[(AOPProxy*)testArray interceptMethodStartForSelector:@selector(addObject:) withInterceptorTarget:self interceptorSelector:@selector( addInterceptor: )]; [(AOPProxy*)testArray interceptMethodEndForSelector:@selector(removeObjectAtIndex:) withInterceptorTarget:self interceptorSelector:@selector( removeInterceptor: )];
因為這兩個方法是AOPProxy的執行個體方法,所以在編寫的時候還是需要在強制轉回來(其實你在XCode裡跟蹤的時候,這裡的testArray一直都是APOProxy類型的對象,因為一開始他就是被AOPPorxy allo出來的)。這兩個方法的實現很簡單,只是將攔截器假如相應的數組中去,待後面取出來執行。
[testArray addObject:[NSNumber numberWithInt:1]]; [testArray removeObjectAtIndex:0];
好了,看起來這裡開始調用某個對象本身的行為了。為什麼說看起來呢?難道不是嗎。當然不是,我在上面已經說過了,這裡只是取名為testArray事實上它並不是NSMutableArray的執行個體,而是AOPProxy的執行個體。但為什麼它還是可以調用addObject這個方法呢,因為它被強制轉換為NSMutableArray類型了,編輯器能夠接受這樣的類型轉換,也就是這是合法的。所以編輯器認為它就是NSMutableArray類型的對象了,所以是可以這麼調用的,但後面你會看到。在運行時其實編譯器知道了它不是真實的NSMutableArray類型(也就是說它無法響應addObject以及removeObjectAtIndex這兩個方法),所以把它交給了另一個專門的方法來處理這些無法響應的訊息:
- (void)forwardInvocation:(NSInvocation *)anInvocation;
這個方法其實是繼承自NSPorxy,NSProxy對它的實現其實就是拋出個異常,子類需要重新實現它,把它訊息傳遞給真實的對象。詳細資料參考官方文檔!
來看看它的實現:
- (void)forwardInvocation:(NSInvocation *)anInvocation;{ SEL aSelector = [anInvocation selector]; // check if the parent object responds to the selector ... if ( [parentObject respondsToSelector:aSelector] ) { [anInvocation setTarget:parentObject]; // // Intercept the start of the method. // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; for ( int i = 0; i < [methodStartInterceptors count]; i++ ) { // first search for this selector ... AOPInterceptorInfo *oneInfo = [methodStartInterceptors objectAtIndex:i]; if ( [oneInfo interceptedSelector] == aSelector ) { // extract the interceptor info id target = [oneInfo interceptorTarget]; SEL selector = [oneInfo interceptorSelector]; // finally invoke the interceptor [(NSObject *) target performSelector:selector withObject:anInvocation]; } } [pool release]; // // Invoke the original method ... // [self invokeOriginalMethod:anInvocation]; // // Intercept the ending of the method. // NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init]; for ( int i = 0; i < [methodEndInterceptors count]; i++ ) { // first search for this selector ... AOPInterceptorInfo *oneInfo = [methodEndInterceptors objectAtIndex:i]; if ( [oneInfo interceptedSelector] == aSelector ) { // extract the interceptor info id target = [oneInfo interceptorTarget]; SEL selector = [oneInfo interceptorSelector]; // finally invoke the interceptor [(NSObject *) target performSelector:selector withObject:anInvocation]; } } [pool2 release]; } // else {// [super forwardInvocation:invocation];// }}
可以砍到這裡讓真實的對象調用了方法,並且幹涉了對象的行為,在其前後加入了攔截器的執行操作。從而“優雅”地實現了AOP。
該庫中,還提供了兩個Aspect:
AOPMethodLoger-用於簡單記錄方法的日誌;
AOPThreadInvoker-用於在一個單獨的線程上執行方法;
之前在Java以及.net中已經很廣泛地應用了AOP的執行個體了,常見的應用有做Log啊,異常捕獲啊之類的。最近在做iOS的應用,其中也會牽扯到異常捕獲的問題,特別是牽扯到資料庫操作以及商務邏輯上的異常,總是寫代碼捕獲塊兒,費事還佔面積。所以,我在裡面又加了一個Aspect:AOPExcettionCatcher。很簡單,就是在這裡統一實現了異常捕獲。
重新實現了invokeOriginalMethod方法:
- (void)invokeOriginalMethod:(NSInvocation *)anInvocation{ NSLog(@"%@",@"entry into try block"); @try { [super invokeOriginalMethod:anInvocation]; } @catch (NSException *exception) { NSLog(@"%@",@"entry into catch block"); NSLog(@"%@",[exception reason]); } @finally { NSLog(@"%@",@"entry into finally block"); }}
當然了這隻是應用之一,你還可以用它做更多的事情。