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)之后