likes
comments
collection
share

【iOS面试题】3.NSProxy是个什么东西?

作者站长头像
站长
· 阅读数 31

在平时的开发中,我们大多数使用的都是NSObject及其子类,而在苹果的Foundation体系下,还有一个与NSObject平级的类,那就是NSProxy。这个类几乎很少用到,这里我们就具体讨论一下。

取自苹果官方文档对NSProxy的定义:

An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.

翻译过来的结果就是:一个为某些对象定义了API的抽象父类,这些对象充当了一个或多个尚不存在的对象的替身。

这里我们要注意几个点:

  • 抽象父类
  • API
  • 替身

抽象父类

作为抽象父类,意味着我们无法直接使用这个类,而必须创建新类来继承它,才可以使用这个类中对应的API。这一点上,NSProxy与常用的NSOperation是一致的。

看一下官方文档中NSProxy的概述部分。

NSProxy implements the basic methods required of a root class, including those defined in the NSObject protocol.

NSProxy实现了根类要求的基本方法,包括那些定义在NSObject协议中的。

However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to.

然而,作为一个抽象类,它没有提供一个初始化方法,并且在接收到任何它不响应的消息时会拉起一个异常。

A concrete subclass must therefore provide an initialization or creation method and override the forwardInvocation: and methodSignatureForSelector: methods to handle messages that it doesn’t implement itself.

因此,一个具体的子类必须提供一个初始化方法,或创建方法,并且重写 forwardInvocation:methodSignatureForSelector: 方法来处理它没有自己实现的消息。

A subclass’s implementation of forwardInvocation: should do whatever is needed to process the invocation, such as forwarding the invocation over the network or loading the real object and passing it the invocation.

子类中关于 forwardInvocation: 的实现应做任何它需要来处理这个invocation的事,比如通过网络转发或加载实际的对象并传递给 invocation

methodSignatureForSelector: is required to provide argument type information for a given message; a subclass’s implementation should be able to determine the argument types for the messages it needs to forward and should construct an NSMethodSignature object accordingly.

methodSignatureForSelector: 被要求提供一个给定消息的类型信息;一个子类实现应该能够决定需要被转发的消息的参数类型,并且应该依此构建一个NSMethodSignature对象。

See the NSDistantObject, NSInvocation, and NSMethodSignature class specifications for more information.

查看 NSDistantObject, NSInvocation, 和 NSMethodSignature 类的说明以获取更多信息。

API

NSProxy的概述中,我们已经基本上明了对NSProxy新建子类所需要做的事:

  1. 提供一个初始化方法
  2. 重写 forwardInvocation:methodSignatureForSelector: 方法

需要提前说明的是, forwardInvocation:methodSignatureForSelector: 两个方法本身就是消息转发过程中参与转发的方法,因此重写这两个方法实际上是在为被替身对象进行消息转发。

具体代码如下:

#import <Foundation/Foundation.h>

@interface OCWeakProxy : NSProxy

@property (nonatomic, weak, readonly) id target;

- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;

@end

================分隔线===================

#import "OCWeakProxy.h"

@interface OCWeakProxy ()

@property (nonatomic, weak, readwrite) id target;

@end

@implementation OCWeakProxy

- (instancetype)initWithTarget:(id)target {
    self.target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[OCWeakProxy alloc] initWithTarget:target];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

@end

需要说明的是,由于NSProxy是抽象类,其继承子类的初始化方法仍然不好写,尤其是Swift版本的,这里暂时先写了OC版本的。

替身

在上述代码中,通过-initWithTarget:+proxyWithTarget:传入的target就是被替身的对象。

这里只展示子页面的代码:

#import "NextViewController.h"
#import "OCWeakProxy.h"

@interface NextViewController ()

@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSInteger count;

@end

@implementation NextViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = @"Next";
    self.view.backgroundColor = [UIColor lightGrayColor];
    self.count = 0;
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 50)];
    button.backgroundColor = [UIColor redColor];
    [button addTarget:self action:@selector(clickOnButton:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
    
}

- (void)clickOnButton:(UIButton *)sender {
    // 通过按钮点击触发定时器,并且将target设置为Proxy
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[OCWeakProxy proxyWithTarget:self] selector:@selector(timerInvoked) userInfo:nil repeats:YES];
}

- (void)timerInvoked {
    self.count++;
    NSLog(@"current count: %ld", self.count);
}

- (void)dealloc {
    [self.timer invalidate];
    NSLog(@"NextViewController is dealloced.");
}

@end

运行结果如下:

【iOS面试题】3.NSProxy是个什么东西?

解析如下:

在整个场景中,存在着这样的引用关系:

【iOS面试题】3.NSProxy是个什么东西?

其中Proxy指向Controller的虚线代表这是一条弱引用,与前面OCWeakProxy中用weak来修饰一致。

当timer运行起来后,会将timerInvoked消息先发送给proxy,由于proxy内部没有实现对应方法,便会进入消息转发流程,最终经过内部重写的forwardInvocation:methodSignatureForSelector: 两个方法将消息转发给controller,并完成调用。

由于Proxy指向Controller的是弱引用,因此当Controller退出时,便会释放,销毁指向timer的引用,并在-dealloc方法中调用timer的-invalidate方法,将timer彻底释放,从RunLoop中移除。

至此,参与引用的对象全部被销毁。

总结

参考上面的例子和官方文档中的描述,我们可以发现使用NSProxy其最大的价值在于其替身的作用和声明的消息转发方法,如果在其子类中进行重写,则可以通过这两个方法实现原来方法调用间对象的消息转发和隔离,避免类似循环引用、高耦合等情况的出现。因此在遇到这些情况时,我们可以酌情考虑使用NSProxy来处理。

转载自:https://juejin.cn/post/6979859480229969957
评论
请登录