NSProxy 使用總結(jié)

NSProxy是一個(gè)虛類激率。它有什么用處呢嗅辣?
OC中類是不支持多繼承的桐猬,要想實(shí)現(xiàn)多繼承一般是有protocol的方式撑蚌,還有一種就是利用NSProxy上遥。有同學(xué)可能會(huì)問(wèn)為什么不用NSObject來(lái)做?同樣都是基類争涌,都支持NSObject協(xié)議粉楚,NSProxy 有的NSObject 都有。但是點(diǎn)進(jìn)NSProxy .h可以看見(jiàn)NSProxy沒(méi)有init方法亮垫,而且NSProxy自身的方法很少模软,是一個(gè)很干凈的類。這點(diǎn)很重要饮潦,因?yàn)镹SObject自身的分類特別多燃异,而消息轉(zhuǎn)發(fā)的機(jī)制是當(dāng)接收者無(wú)法處理時(shí)才會(huì)通過(guò)forwardInvocation:來(lái)尋求能夠處理的對(duì)象.在日常使用時(shí),我們很難避免不使用NSObject 的分類方法比如valueForKey這個(gè)方法NSObject就不會(huì)轉(zhuǎn)發(fā)继蜡。詳解可見(jiàn)http://www.reibang.com/p/5bfcc32c21c0


然后我們具體來(lái)看看NSProxy 都用來(lái)做什么回俐。

1.多繼承

虛基類本身就是為了解決繼承問(wèn)題而生,而OC的動(dòng)態(tài)特性使得我們可以利用NSProxy做多繼承:
首先我們新建兩個(gè)基類如下:

@implementation classA
-(void)infoA{
    NSLog(@"這里 是 classA 壹瘟,我是賣水的");
    
}
@end
@implementation classB
-(void)infoB{
    NSLog(@"這里 是 classB 我是賣飯的");
}
@end

classA 賣水 classB 賣飯鲫剿,但是分開(kāi)買太麻煩,所以我找了一個(gè)代理如下

@interface ClassProxy : NSProxy
@property(nonatomic,strong,readonly)NSMutableArray *targetArray;
-(void)target:(id)target;
-(void)handleTargets:(NSArray *)targets;
@end

NSProxy 必須以子類的形式出現(xiàn)稻轨。
因?yàn)榭紤]到很可能還有其他的賣衣服的灵莲,賣鞋子的需要ClassProxy來(lái)代理,這邊做了一個(gè)數(shù)組來(lái)存放需要代理的類

@interface ClassProxy()
@property(nonatomic,strong)NSMutableArray *targetArray;//多個(gè)targets皆可代理
@property(nonatomic,strong)NSMutableDictionary *methodDic;
@property(nonatomic,strong)id target;
@end

然后target 和 相對(duì)應(yīng)的method demo做了一個(gè)字典來(lái)存儲(chǔ)殴俱,方便獲取

-(void)registMethodWithTarget:(id)target{
    unsigned int countOfMethods = 0;
    Method *method_list = class_copyMethodList([target class], &countOfMethods);
    for (int i = 0; i<countOfMethods; i++) {
        Method method = method_list[i];
        //得到方法的符號(hào)
        SEL sel = method_getName(method);
        //得到方法的符號(hào)字符串
        const char *sel_name = sel_getName(sel);
        //得到方法的名字
        NSString *method_name = [NSString stringWithUTF8String:sel_name];
        self.methodDic[method_name] = target;
    }
    free(method_list);
}

然后就是最主要的兩個(gè)方法

-(void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = invocation.selector;
    NSString *methodName = NSStringFromSelector(sel);
    id target = self.methodDic[methodName];
    if (target) {
        [invocation invokeWithTarget:target];
    }
    
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    NSMethodSignature *Method;
    NSString *methodName = NSStringFromSelector(sel);
    id target = self.methodDic[methodName];
    if (target) {
        Method =  [target methodSignatureForSelector:sel];
    }else{
        Method = [super methodSignatureForSelector:sel];
    }
    return Method;
}

methodSignatureForSelector:得到對(duì)應(yīng)的方法簽名政冻,通過(guò)forwardInvocation:轉(zhuǎn)發(fā)
下面看一下調(diào)用和打印結(jié)果

- (void)viewDidLoad {
    [super viewDidLoad];
//    [self analysis];
    [self classInheritance];
    // Do any additional setup after loading the view, typically from a nib.
}
//多繼承
-(void)classInheritance{
    classA *A = [[classA alloc]init];
    classB *B = [[classB alloc]init];
    ClassProxy *proxy = [ClassProxy alloc];
    [proxy handleTargets:@[A,B]];
    [proxy performSelector:@selector(infoA)];
    [proxy performSelector:@selector(infoB)];
    
}
2018-12-27 18:02:34.445 NSProxyStudy[18975:4587631] 這里 是 classA ,我是賣水的
2018-12-27 18:02:34.446 NSProxyStudy[18975:4587631] 這里 是 classB 我是賣飯的

以上就是利用NSProxy 實(shí)現(xiàn)多繼承线欲。

2.避免循環(huán)應(yīng)用

這里舉了比較常見(jiàn)了一個(gè)例子NSTimer.
由于目前蘋果在iOS10以上明场,已經(jīng)給出了timer 的block方式,已經(jīng)可以解決循環(huán)引用的問(wèn)題李丰。所以demo舉例只是說(shuō)明利用NSProxy如何解決循環(huán)引用苦锨,大家在使用的時(shí)候可直接使用系統(tǒng)的方法。
首先因?yàn)镹STimer創(chuàng)建的時(shí)候需要傳入一個(gè)target,并且持有它舟舒,而target本身也會(huì)持有timer所以會(huì)造成循環(huán)引用拉庶。所以我們將target 用NSProxy的子類代替如下

-(void)viewDidLoad{
    [super viewDidLoad];
    self.timer = [NSTimer timerWithTimeInterval:1
                                         target:[WeakProxy proxyWithTarget:self]
                                       selector:@selector(invoked:)
                                       userInfo:nil
                                        repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)invoked:(NSTimer *)timer{
    NSLog(@"1");
}

在WeakProxy中我們?cè)O(shè)定target 為弱引用如下

@interface WeakProxy ()
@property(nonatomic,weak)id target;
@end

@implementation WeakProxy
-(instancetype)initWithTarget:(id)target{
    self.target = target;
    return self;
}
+(instancetype)proxyWithTarget:(id)target{
    return [[self alloc] initWithTarget:target];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = invocation.selector;
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}
@end

然后同樣利用上述兩個(gè)方法進(jìn)行消息轉(zhuǎn)發(fā)即可。

AOP面向切片編程

iOS中面向切片編程一般有兩種方式 秃励,一個(gè)是直接基于runtime 的method-Swizzling.還有一種就是基于NSProxy
我們先創(chuàng)建一個(gè)子類AOPProxy

typedef void(^proxyBlock)(id target,SEL selector);

NS_ASSUME_NONNULL_BEGIN

@interface AOPProxy : NSProxy
+(instancetype)proxyWithTarget:(id)target;
-(void)inspectSelector:(SEL)selector preSelTask:(proxyBlock)preTask endSelTask:(proxyBlock)endTask;
@end

-(void)inspectSelector:(SEL)selector preSelTask:(proxyBlock)preTask endSelTask:(proxyBlock)endTask;
第一個(gè)參數(shù)是需要hook的方法名字 后面兩個(gè)分別是hook 該方法后 執(zhí)行前需要執(zhí)行的block 和 執(zhí)行后的需要執(zhí)行的block

@interface AOPProxy ()
@property(nonatomic,strong)id target;
@property(nonatomic,strong)NSMutableDictionary *preSelTaskDic;
@property(nonatomic,strong)NSMutableDictionary *endSelTaskDic;
@end

然后創(chuàng)建兩個(gè)字典氏仗,分別存放 不同selector 對(duì)應(yīng)的執(zhí)行block(可能一個(gè)target有好幾個(gè)方法需要被hook)
然后我們來(lái)看一下執(zhí)行

-(void)inspect{
    NSMutableArray *targtArray = [AOPProxy proxyWithTarget:[NSMutableArray arrayWithCapacity:1]];
    [(AOPProxy *)targtArray inspectSelector:@selector(addObject:) preSelTask:^(id target, SEL selector) {
        [target addObject:@"-------"];
        NSLog(@"%@我加進(jìn)來(lái)之前",target);
    } endSelTask:^(id target, SEL selector) {
        [target addObject:@"-------"];
        NSLog(@"%@我加進(jìn)來(lái)之后",target);
    }];
    [targtArray addObject:@"我是一個(gè)元素"];
}

結(jié)果為

2018-12-28 11:57:05.590 NSProxyStudy[23812:4840464] (
    "-------"
)我加進(jìn)來(lái)之前
2018-12-28 11:57:05.591 NSProxyStudy[23812:4840464] (
    "-------",
    "\U6211\U662f\U4e00\U4e2a\U5143\U7d20",
    "-------"
)我加進(jìn)來(lái)之后

demo地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市夺鲜,隨后出現(xiàn)的幾起案子皆尔,更是在濱河造成了極大的恐慌,老刑警劉巖币励,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慷蠕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡食呻,警方通過(guò)查閱死者的電腦和手機(jī)砌们,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搁进,“玉大人,你說(shuō)我怎么就攤上這事昔头”剩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵揭斧,是天一觀的道長(zhǎng)莱革。 經(jīng)常有香客問(wèn)我,道長(zhǎng)讹开,這世上最難降的妖魔是什么盅视? 我笑而不...
    開(kāi)封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮旦万,結(jié)果婚禮上闹击,老公的妹妹穿的比我還像新娘。我一直安慰自己成艘,他們只是感情好赏半,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著淆两,像睡著了一般断箫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秋冰,一...
    開(kāi)封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天仲义,我揣著相機(jī)與錄音,去河邊找鬼。 笑死埃撵,一個(gè)胖子當(dāng)著我的面吹牛赵颅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盯另,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼性含,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鸳惯?” 一聲冷哼從身側(cè)響起商蕴,我...
    開(kāi)封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芝发,沒(méi)想到半個(gè)月后绪商,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辅鲸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年格郁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片独悴。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡例书,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刻炒,到底是詐尸還是另有隱情决采,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布坟奥,位于F島的核電站树瞭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爱谁。R本人自食惡果不足惜晒喷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望访敌。 院中可真熱鬧凉敲,春花似錦、人聲如沸捐顷。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迅涮。三九已至废赞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叮姑,已是汗流浹背唉地。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工据悔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耘沼,地道東北人极颓。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像群嗤,于是被迫代替她去往敵國(guó)和親菠隆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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