OC中不繼承于NSObject 的類NSProxy

NSObject類是Objective-C中大部分類的基類。

但不是很多人知道除了NSObject之外的另一個(gè)基類——NSProxy.

總的來說寞埠,NSProxy是一個(gè)虛類崖技,你可以通過繼承它稿静,并重寫這兩個(gè)方法以實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例:

- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel ;

現(xiàn)在NSProxy的真面目終于浮出水面:負(fù)責(zé)將消息轉(zhuǎn)發(fā)到真正的target的代理類娇掏。舉個(gè)例子,你想要賣一件二手物品空执,但是你并不想直接跟賣家接觸(直接向target發(fā)消息)浪箭,這時(shí)你去找了一個(gè)第三方,你告訴這個(gè)第三方你要買什么辨绊、出多少錢買奶栖、什么時(shí)候要等(向代理發(fā)消息),第三方再去跟賣家接觸并把這些信息轉(zhuǎn)告賣家(轉(zhuǎn)發(fā)消息給真實(shí)的target),最后通過第三方去完成這個(gè)交易宣鄙。
了解完NSProxy是是什么以后袍镀,那么它究竟能幫我們干些什么呢?
通過NSProxy在Objective-C中模擬多繼承
多繼承在編程中可以說是比較有用的特性冻晤。舉個(gè)例子苇羡,原本有兩個(gè)相互獨(dú)立的類A和類B,它們各自繼承各自的父類鼻弧,項(xiàng)目進(jìn)行地好好的设江,突然有一天產(chǎn)品經(jīng)理過來告訴你,我要在下個(gè)版本加一個(gè)xxxxx的特性攘轩,非常緊急叉存。一臉懵逼的你發(fā)現(xiàn)如果要實(shí)現(xiàn)這個(gè)特性,你需要對(duì)類A以及其父類作很大的修改度帮,代價(jià)非常之高歼捏。突然你意識(shí)到原來類B的父類已經(jīng)有類似的功能,你只需要讓類A繼承于類B的父類并重寫其某些方法就能實(shí)現(xiàn)够傍,這樣做高效且低風(fēng)險(xiǎn)甫菠,于是你屁顛屁顛地?cái)]起了代碼挠铲。
可是冕屯,Objective-C卻不支持這樣一個(gè)強(qiáng)大的特性。不過NSProxy可以幫我們?cè)谀撤N程度上(這只是一個(gè)模擬的多繼承拂苹,并不是完全的多繼承)解決這個(gè)問題:
現(xiàn)在假設(shè)我們想要去買書安聘,但是我懶癌犯了,不想直接去書店(供應(yīng)商)買瓢棒,如果有一個(gè)跑腿的人(經(jīng)銷商)幫我去書店買完浴韭,我再跟他買。同時(shí)脯宿,我買完書又想買件衣服念颈,我又可以很輕松地在他那里買到一件衣服(多繼承)。
首先连霉,我們定義BookProvider類與ClothesProvider類作為基類榴芳。

TDBookProvider###

// TDBookProvider.h
#import 

@protocol TDBookProviderProtocol 
- (void)purchaseBookWithTitle:(NSString *)bookTitle;
@end

@interface TDBookProvider : NSObject
@end

// TDBookProvider.m
#import "TDBookProvider.h" 
@interface TDBookProvider () 
@end
@implementation TDBookProvider

- (void)purchaseBookWithTitle:(NSString *)bookTitle{
    NSLog(@"You've bought \"%@\"",bookTitle);
}
@end

TDClothesProvider.h

//  TDClothesProvider.h

#import 

typedef NS_ENUM (NSInteger, TDClothesSize){
    TDClothesSizeSmall = 0,
    TDClothesSizeMedium,
    TDClothesSizeLarge
};

@protocol TDClothesProviderProtocol 

- (void)purchaseClothesWithSize:(TDClothesSize )size;

@end

@interface TDClothesProvider : NSObject

@end

//  TDClothesProvider.m

#import "TDClothesProvider.h"

@interface TDClothesProvider () 

@end

@implementation TDClothesProvider

- (void)purchaseClothesWithSize:(TDClothesSize )size{
    NSString *sizeStr;
    switch (size) {
        case TDClothesSizeLarge:
            sizeStr = @"large size";
            break;
        case TDClothesSizeMedium:
            sizeStr = @"medium size";
            break;
        case TDClothesSizeSmall:
            sizeStr = @"small size";
            break;
        default:
            break;
    }
    NSLog(@"You've bought some clothes of %@",sizeStr);
}
@end
//  TDDealerProxy.h

#import 
#import "TDBookProvider.h"
#import "TDClothesProvider.h"

@interface TDDealerProxy : NSProxy 

+ (instancetype )dealerProxy;

@end

//  TDDealerProxy.m

#import "TDDealerProxy.h"
#import <objc/runtime.h>

@interface TDDealerProxy () {
    TDBookProvider          *_bookProvider;
    TDClothesProvider       *_clothesProvider;
    NSMutableDictionary     *_methodsMap;
}
@end

@implementation TDDealerProxy

#pragma mark - class method
+ (instancetype)dealerProxy{
    return [[TDDealerProxy alloc] init];
}

#pragma mark - init
- (instancetype)init{
    _methodsMap = [NSMutableDictionary dictionary];
    _bookProvider = [[TDBookProvider alloc] init];
    _clothesProvider = [[TDClothesProvider alloc] init];

    //映射target及其對(duì)應(yīng)方法名
    [self _registerMethodsWithTarget:_bookProvider];
    [self _registerMethodsWithTarget:_clothesProvider];

    return self;
}

#pragma mark - private method
- (void)_registerMethodsWithTarget:(id )target{

    unsigned int numberOfMethods = 0;

    //獲取target方法列表
    Method *method_list = class_copyMethodList([target class], &numberOfMethods);

    for (int i = 0; i < numberOfMethods; i ++) {
        //獲取方法名并存入字典
        Method temp_method = method_list[i];
        SEL temp_sel = method_getName(temp_method);
        const char *temp_method_name = sel_getName(temp_sel);
        [_methodsMap setObject:target forKey:[NSString stringWithUTF8String:temp_method_name]];
    }

    free(method_list);
}
#pragma mark - NSProxy override methods
- (void)forwardInvocation:(NSInvocation *)invocation{
    //獲取當(dāng)前選擇子
    SEL sel = invocation.selector;

    //獲取選擇子方法名
    NSString *methodName = NSStringFromSelector(sel);

    //在字典中查找對(duì)應(yīng)的target
    id target = _methodsMap[methodName];

    //檢查target
    if (target && [target respondsToSelector:sel]) {
        [invocation invokeWithTarget:target];
    } else {
        [super forwardInvocation:invocation];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    //獲取選擇子方法名
    NSString *methodName = NSStringFromSelector(sel);

    //在字典中查找對(duì)應(yīng)的target
    id target = _methodsMap[methodName];

    //檢查target
    if (target && [target respondsToSelector:sel]) {
        return [target methodSignatureForSelector:sel];
    } else {
        return [super methodSignatureForSelector:sel];
    }
}

@end

這里有兩個(gè)要注意的問題:1、TDDealerProxy這個(gè)子類必須要遵循之前定義的兩個(gè)協(xié)議TDBookProviderProtocol與TDClothesProviderProtocol跺撼,目的是騙過編譯器窟感,讓編譯器認(rèn)為這個(gè)類實(shí)現(xiàn)了上面兩個(gè)協(xié)議2、NSProxy類是沒有init方法的歉井,也就是說如果我們要獲得一個(gè)NSProxy的實(shí)例柿祈,代碼只需要這樣:

NSProxy *proxyInstance = [NSProxy alloc];

在AppDelagate 中 實(shí)現(xiàn):###

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    TDDealerProxy *dealerProxy = [TDDealerProxy dealerProxy];
    [dealerProxy purchaseBookWithTitle:@"Swift 100 Tips"];
    [dealerProxy purchaseClothesWithSize:TDClothesSizeMedium];

    // Override point for customization after application launch.
    return YES;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躏嚎,更是在濱河造成了極大的恐慌蜜自,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件紧索,死亡現(xiàn)場(chǎng)離奇詭異袁辈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)珠漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門晚缩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人媳危,你說我怎么就攤上這事荞彼。” “怎么了待笑?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鸣皂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我暮蹂,道長(zhǎng)寞缝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任仰泻,我火速辦了婚禮荆陆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘集侯。我一直安慰自己被啼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布棠枉。 她就那樣靜靜地躺著浓体,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辈讶。 梳的紋絲不亂的頭發(fā)上命浴,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音贱除,去河邊找鬼生闲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勘伺,可吹牛的內(nèi)容都是我干的跪腹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼飞醉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼冲茸!你這毒婦竟也來了屯阀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤轴术,失蹤者是張志新(化名)和其女友劉穎难衰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逗栽,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盖袭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彼宠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鳄虱。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凭峡,靈堂內(nèi)的尸體忽然破棺而出拙已,到底是詐尸還是另有隱情,我是刑警寧澤摧冀,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布倍踪,位于F島的核電站,受9級(jí)特大地震影響索昂,放射性物質(zhì)發(fā)生泄漏建车。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一椒惨、第九天 我趴在偏房一處隱蔽的房頂上張望缤至。 院中可真熱鬧,春花似錦框产、人聲如沸凄杯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屯碴,卻和暖如春描睦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背导而。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工忱叭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人今艺。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓韵丑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親虚缎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撵彻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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