談談NSProxy

概念

NSProxy是一個類似于NSObject的根類,看代碼:

NS_ROOT_CLASS
@interface NSProxy <NSObject>{
    Class   isa;
}

上面我們可以看到NSProxy是一個實現了NSObject協議的根類。
蘋果的官方文檔是這樣描述它的:
NSProxy 是一個抽象基類,它為一些表現的像是其它對象替身或者并不存在的對象定義API。一般的,發送給代理的消息被轉發給一個真實的對象或者代理本身引起加載(或者將本身轉換成)一個真實的對象。NSProxy的基類可以被用來透明的轉發消息或者耗費巨大的對象的lazy 初始化。

NSProxy實現了包括NSObject協議在內基類所需的基礎方法,但是作為一個抽象的基類并沒有提供初始化的方法。它接收到任何自己沒有定義的方法他都會產生一個異常,所以一個實際的子類必須提供一個初始化方法或者創建方法,并且重載forwardInvocation:方法和methodSignatureForSelector:方法來處理自己沒有實現的消息。一個子類的forwardInvocation:實現應該采取所有措施來處理invocation,比如轉發網絡消息,或者加載一個真實的對象,并把invocation轉發給他。methodSignatureForSelector:需要為給定消息提供參數類型信息,子類的實現應該有能力決定他應該轉發消息的參數類型,并構造相對應的NSMethodSignature對象。詳細信息可以查看NSDistantObject, NSInvocation, and NSMethodSignature的類型說明。
相信看了這些描述我們應該能對NSProxy有個初步印象,它僅僅是個轉發消息的場所,至于如何轉發,取決于派生類到底如何實現的。比如我們可以在內部hold住(或創建)一個對象,然后把消息轉發給該對象。那我們就可以在轉發的過程中做些手腳了。甚至也可以不去創建這些對象,去做任何你想做的事情,但是必須要實現他的forwardInvocation:和methodSignatureForSelector:方法。

用途

  1. 現在比較流行的說法是用它來模擬多重繼承,大致過程就是讓它Hold住你要實現多繼承的類的對象,然后被hold住的對象的行為定義在接口中,并讓Proxy去實現這些接口。然后再轉發的時候把消息轉發到實現了該接口的對象去執行,這樣就好像實現了多重繼承一樣。注意:這個真不是多重繼承,只是包含,然后把消息路由到指定的對象而已,其實完全可以用NSObject類來實現。
  2. 另外一個功能也是我們要重點介紹的功能就是AOP(Aspect Oriented Programming),它是可以通過預編譯方式和運行期動態代理實現再不修改源代碼的情況下給程序動態添加功能的一種技術。因為OC的動態語言特性,所以再OC里實現AOP也有多種方式,比如使用Runtime的swizzle method機制來實現方法替換從而達到Hook的目的(后面盡量抽時間寫一篇文章來介紹,也當自己給自己備注一下)。
  3. Mac編程里的分布式編程的應用。
  4. 其他:腦洞大開的程序員們可以把任何符合當前場景的工作放到這里。

AOP與NSProxy

OC的動態語言的核心部分應該就是objc_msgSend方法的調用了。該函數的聲明大致如下:
id objc_msgSend(id self, SEL _cmd, ...)
其中第一個參數是接受消息的target,第二個參數是要執行的selector,也就是我們要調用的方法,后面可以接若干個要傳給selector的參數。
那只要我們能夠Hook到對某個對象的objc_msgSend的調用,并且可以修改其參數甚至于修改成任意其他selector的IMP,我們就實現了AOP。
讓我們來做一個簡單的demo看看能不能完成我們希望的功能。

@interface MyProxy : NSProxy{
//再內部hold住一個要hook的對象
id _innerObject;
}
+(instancetype)proxyWithObj:(id)object;
@end

@interface Dog : NSObject
-(NSString *)barking:(NSInteger)months;
@end

再來看實現部分


@implementation MyProxy
+(instancetype)proxyWithObj:(id)object{
    MyProxy *proxy = [MyProxy alloc];
    //hold住要hook的對象
    proxy->_innerObject = object;
    //注意返回的值是Proxy對象
    return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    //這里可以返回任何NSMethodSignature對象,你也可以完全自己構造一個
    return [_innerObject methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
    if([_innerObject respondsToSelector:invocation.selector]){
        NSString *selectorName = NSStringFromSelector(invocation.selector);
        NSLog(@"Before calling %@",selectorName);
        [invocation retainArguments];
        NSMethodSignature *sig = [invocation methodSignature];
        //獲取參數個數,注意再本例里這里的值是3,為什么呢?
        //對,就是因為objc_msgSend的前兩個參數是隱含的
        NSUInteger cnt = [sig numberOfArguments];
        //本例只是簡單的將參數和返回值打印出來
        for (int i = 0; i < cnt; i++) {
            const char * type = [sig getArgumentTypeAtIndex:i];
            if(strcmp(type, "@") == 0){
                NSObject *obj;
                [invocation getArgument:&obj atIndex:i];
                //這里輸出的是:"parameter (0)'class is MyProxy"
                //也證明了這是objc_msgSend的第一個參數
                NSLog(@"parameter (%d)'class is %@",i,[obj class]);
            }
            else if(strcmp(type, ":") == 0){
                SEL sel;
                [invocation getArgument:&sel atIndex:i];
                //這里輸出的是:"parameter (1) is barking:"
                //也就是objc_msgSend的第二個參數
                NSLog(@"parameter (%d) is %@",i,NSStringFromSelector(sel));
            }
            else if(strcmp(type, "q") == 0){
                int arg = 0;
                [invocation getArgument:&arg atIndex:i];
                //這里輸出的是:"parameter (2) is int value is 4"
                //稍后會看到我們再調用barking的時候傳遞的參數就是4
                NSLog(@"parameter (%d) is int value is %d",i,arg);
            }
        }
        //消息轉發
        [invocation invokeWithTarget:_innerObject];
        const char *retType = [sig methodReturnType];
        if(strcmp(retType, "@") == 0){
            NSObject *ret;
            [invocation getReturnValue:&ret];
            //這里輸出的是:"return value is wang wang!"
            NSLog(@"return value is %@",ret);
        }
        NSLog(@"After calling %@",selectorName);
    }
}
@end

@implementation Dog
-(NSString *)barking:(NSInteger)months{
    return months > 3 ? @"wang wang!" : @"eng eng!";
}
@end

函數的調用如下:

Dog *dog = [MyProxy proxyWithObj:[Dog alloc]];
[dog barking:4];

從上面的代碼我們可以看到,我們可以任意更改參數,調用的方法,甚至轉發給其他類型的對象,這確實達到了Hook對象的目的,也就是可以實現AOP的功能了。

好在找工作期間有時間,年紀大了寫點東西也好,將來能給自己做個備忘。寫的好累有木有啊?不足之處希望大家批評指點!
吐槽一下:校長這個昵稱真的很火么?試了好幾個都不成功!哈哈

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容