NSProxy使用

657 阅读5分钟

概念

NSProxy是一个类似于NSObject的根类,看代码:

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

上面我们可以看出NSProxy是一个实现了NSObject协议的抽象的基类,是根类,与NSObject类似

用法

消息转发

NSProxy实现了包括NSObject协议在内基类所需的基础方法,但是作为一个抽象的基类并没有提供初始化的方法.它接收到任何自己没有定义的方法他都会产生一个异常,所以一个实际的子类必须提供一个初始化方法或者创建方法,并且重载forwardInvocation:方法和methodSignatureForSelector:方法来处理自己没有实现的消息。这也是NSProxy的主要功能,负责把消息转发给真正的target的代理类,NSProxy正是代理的意思。那它是如何转发消息的呢

  1. 先设置一个类GTProxy,继承NSProxy
  2. 为GTProxy设置一个NSObject 属性
  3. 自定义一个转换方法,相当于给 NSObject 属性赋值
  4. 然后通过这个属性获得调用方法的方法签名methodSignatureForSelector:
  5. 为调用设置目标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来实现弱引用

  1. 为外界暴露一个变身方法:
@interface GTProxy : NSProxy
- (instancetype)initWithTarget:(id)target;

+ (instancetype)proxyWithTarget:(id)target;

@end
  1. 设置一个 target属性
@interface GTProxy()
/**/
@property (nonatomic,weak) id target;
@end
  1. 实现变身方法
- (instancetype)initWithTarget:(id)target
{
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target
{
    return [[self alloc] initWithTarget:target];
}
  1. 重写- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel方法获得方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}
  1. 重写- (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];
}
  1. 调用
  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处理