概念
NSProxy是一个类似于NSObject的根类,看代码:
NS_ROOT_CLASS
@interface NSProxy <NSObject>{
Class isa;
}
复制代码
上面我们可以看出NSProxy是一个实现了NSObject协议的抽象的基类,是根类,与NSObject类似
用法
消息转发
NSProxy实现了包括NSObject协议在内基类所需的基础方法,但是作为一个抽象的基类并没有提供初始化的方法.它接收到任何自己没有定义的方法他都会产生一个异常,所以一个实际的子类必须提供一个初始化方法或者创建方法,并且重载forwardInvocation:方法和methodSignatureForSelector:方法来处理自己没有实现的消息。这也是NSProxy的主要功能,负责把消息转发给真正的target的代理类,NSProxy正是代理的意思。那它是如何转发消息的呢
- 先设置一个类GTProxy,继承NSProxy
- 为GTProxy设置一个NSObject 属性
- 自定义一个转换方法,相当于给 NSObject 属性赋值
- 然后通过这个属性获得调用方法的方法签名methodSignatureForSelector:
- 为调用设置目标forwardInvocation:
代码如下:
@interface GTProxy()
/**/
@property (nonatomic,strong) NSObject *object;
@end
@implementation GTProxy
/*
NSProxy实现了包括NSObject协议在内基类所需的基础方法,但是作为一个抽象的基类并没有提供初始化的方法。它接收到任何自己没有定义的方法他都会产生一个异常,所以一个实际的子类必须提供一个初始化方法或者创建方法,并且重载forwardInvocation:方法和methodSignatureForSelector:方法来处理自己没有实现的消息
*/
- (id)transformToObject:(NSObject *)object
{
self.object = object;
return self.object;
}
//获得调用方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
NSMethodSignature *signature;
if (_object) {
signature = [self.object methodSignatureForSelector:sel];
} else {
signature= [super methodSignatureForSelector:sel];
}
return signature;
}
//为调用设置目标
- (void)forwardInvocation:(NSInvocation *)invocation
{
if (self.object) {
[invocation invokeWithTarget:self.object];
}
}
复制代码
避免循环引用
一些开源项目中使用NSProxy来避免循环引用,用在NSTimer或者CADisplayLink中
NSTimer是一个需要添加到Runloop里的类,对于一个不会自动停止的Timer,你需要调用invalidate方法来手动断开这个Timer。否则,引用Timer的Controller或者其他类,就会出现循环引用而无法释放掉
举个例子,在Controller中,添加Timer很常见,比如
#import "ViewController.h"
@interface ViewController ()
@property (strong,nonatomic)NSTimer * timer;
@end
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1
target:self
selector:@selector(timerInvoked:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerInvoked:(NSTimer *)timer{
NSLog(@"1");
}
- (void)dealloc{
NSLog(@"Dealloc");
}
复制代码
假如我Push这样一个ViewController,然后pop。
你会发现Controller没有被释放,timer也没有被取消。
我们可以在dealloc中,调用Timer取消吗?比如
- (void)dealloc{
[self.timer invalidate];
NSLog(@"Dealloc");
}
复制代码
当然不行,因为Controller根本没有被释放,dealloc方法根本不会调用。 当然,破坏这种循环引用的方式有很多种。下面主要讲解如何用NSProxy来破坏 我们写一个GTProxy来实现弱引用
- 为外界暴露一个变身方法:
@interface GTProxy : NSProxy
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
复制代码
- 设置一个 target属性
@interface GTProxy()
/**/
@property (nonatomic,weak) id target;
@end
复制代码
- 实现变身方法
- (instancetype)initWithTarget:(id)target
{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target
{
return [[self alloc] initWithTarget:target];
}
复制代码
- 重写- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel方法获得方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
复制代码
- 重写- (void)forwardInvocation:(NSInvocation *)invocation方法改变调用对象,也就是说,让消息实际上发给真正的实现这个方法的类
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL sel = [invocation selector];
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [self.target respondsToSelector:aSelector];
}
复制代码
- 调用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:[GTProxy proxyWithTarget:self] selector:@selector(fire) userInfo:nil repeats:YES];
复制代码
模拟多继承
多继承可以看作是单继承的扩展。所谓多继承是指派生类具有多个基类,派生类与每个基类之间的关系仍可看作是一个单继承。大家知道,Objective-C不支持多继承,但是NSProcy可以在一定程度上解决这个问题,但需要注意的是,这只是一个模拟的多继承,并不是完全的多继承。 使用方法和转发机制一样,下面看一个使用案例
GTProxy *px = [GTProxy alloc];
NSMutableArray *array = [NSMutableArray array];
[px transformToObject:array];
[px performSelector:@selector(addObject:) withObject:@"123"];
NSLog(@"%@",array);
NSMutableString *string = [NSMutableString string];
[px transformToObject:string];
[px performSelector:@selector(appendString:) withObject:@"jia"];
NSLog(@"%@",string);
复制代码
注意:直接调用 [px addObject:@"123"];是不行的,因为GTProxy只是将调用方法的对象只给NSMutableArray对象,并不是真正实现了NSMutableArray的方法
和NSObject区别
虽然NSProxy和class NSObject都定义了-forwardInvocation:和-methodSignatureForSelector:,但这两个方法并没有在protocol NSObject中声明;两者对这俩方法的调用逻辑更是完全不同。
对于class NSObject而言,接收到消息后先去自身的方法列表里找匹配的selector,如果找不到,会沿着继承体系去superclass的方法列表找;如果还找不到,先后会经过+resolveInstanceMethod:和-forwardingTargetForSelector:处理,处理失败后,才会到-methodSignatureForSelector:/-forwardInvocation:进行最后的挣扎. 但对于NSProxy,接收unknown selector后,直接回调-methodSignatureForSelector:/-forwardInvocation:,消息转发过程比class NSObject要简单得多。
相对于class NSObject,NSProxy的另外一个非常重要的不同点也值得注意:NSProxy会将自省相关的selector直接forward到-forwardInvocation:回调中,这些自省方法包括:
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
复制代码
简单来说,这4个selector的实际接收者realObject,而不是NSProxy对象本身。但另一方面,NSProxy并没有将performSelector系列selector也forward到-forwardInvocation:,换句话说,[proxy performSelector:someSelector]的真正处理者仍然是proxy自身,只是后续会将someSelector给forward到-forwardInvocation:回调,然后经由realObject处理