NSProxy

在看一些开源项目的时候,发现有使用NSProxy这个类

什么是NSProxy

  • NSProxy是一个抽象的基类,是根类,与NSObject类似
  • NSProxyNSObject都实现了<NSObject>协议
  • 提供了消息转发的通用接口

如何使用NSProxy来转发消息?
1.需要继承NSProxy
2.重写如下的2个方法:

methodSignatureForSelector:方法,返回的是一个NSMethodSignature类型,来描述给定selector的参数和返回值类型。返回nil,表示proxy不能识别指定的selector。所有的NSObject也响应此消息。
forwardInvocation:方法将消息转发到对应的对象上

用法

NSProxy实现多继承

参考:

避免循环引用

一些开源项目中使用NSProxy来避免循环引用,用在NSTimer或者CADisplayLink

以下内容来自NSProxy与消息转发机制,也可参考:

NSTimer是一个需要添加到Runloop里的类,对于一个不会自动停止的Timer,你需要调用invalidate方法来手动断开这个Timer。否则,引用Timer的Controller或者其他类,就会出现循环引用而无法释放掉。
举个例子,在Controller中,添加Timer很常见,比如

#import "SecondViewController.h"
@interface SecondViewController ()

@property (strong,nonatomic)NSTimer * timer;

@end
@implementation SecondViewController

- (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");
}
@end

假如我Push这样一个SecondViewController,然后pop。
你会发现Controller没有被释放,timer也没有被取消。

我们可以在dealloc中,调用Timer取消吗?比如

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

当然不行,因为Controller根本没有被释放,dealloc方法根本不会调用。
当然,破坏这种循环引用的方式有很多种。本文主要讲解如何用NSProxy来破坏。
我们写一个WeakProxy来实现弱引用

@interface WeakProxy : NSProxy
@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end

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

然后,这样创建Timer

self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerInvoked:) userInfo:nil repeats:YES];

你会发现可以释放了。

原理如下:

这里写图片描述

我们把虚线处变成了弱引用。于是,Controller就可以被释放掉,我们在Controller的dealloc中调用invalidate,就断掉了Runloop对Timer的引用,于是整个三个淡蓝色的就都被释放掉了。


延迟初始化

参考利用 NSProxy 实现弱引用和延迟初始化

使用场景:

  • 第一种情况,在 [SomeClass lazy] 之后调用 doSomthing,首先进入 forwardingTargetForSelector_object 为 nil 并且不是 init 开头的方法的时候会调用 init 初始化对象,然后将消息转发给代理对象 _object
  • 第二种情况,在 [SomeClass lazy] 之后调用 initWithXXX:,首先进入 forwardingTargetForSelector 返回 nil,然后进入 methodSignatureForSelector:forwardInvocation: 保存自定义初始化方法的调用,最后调用 doSomthing,进入 forwardingTargetForSelector,_object 为 nil 并且不是 init 开头的方法的时候会调用自定义初始化方法,然后将消息转发给代理对象 _object;
SomeClass *object = [SomeClass lazy];
// other thing ...
[object doSomething];// 在这里,object 才会调用初始化方法,然后调用 doSomething

具体实现(代码来源:Lazy Initialization for Objective-C):

LazyInitialization.m

#import "LazyInitialization.h"
@interface LazyProxy : NSProxy
- (instancetype)initWithClass:(Class)class;
@end
@implementation NSObject (LazyInitialization)
+ (instancetype)lazy {
    return (id)[[LazyProxy alloc] initWithClass:[self class]];
}
@end
@implementation LazyProxy {
    id _object;// 代理对象
    Class _objectClass;// 代理类
    NSInvocation *_initInvocation;// 自定义 init 调用
}
- (instancetype)initWithClass:(Class)cls {
    _objectClass = cls;
    return self;
}
- (void)instantiateObject {
    _object = [_objectClass alloc];
    if (_initInvocation == nil) {// 允许一些类 [SomeClass lazy] (没有调用 init)
        _object = [_object init];
    } else {// 调用自定义 init 方法
        [_initInvocation invokeWithTarget:_object];
        [_initInvocation getReturnValue:&_object];
        _initInvocation = nil;
    }
}
- (id)forwardingTargetForSelector:(SEL)selector {
    if (_object == nil) {// _object 没有初始化
        if (![NSStringFromSelector(selector) hasPrefix:@"init"]) {// 调用 init 开头之外的方法前进行 _object 初始化
            [self instantiateObject];
        }
    }
    return _object;// 将消息转发给 _object
}
// 调用自定义 init 方法会进入这个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature *signature = [_objectClass instanceMethodSignatureForSelector:selector];
    return signature;
}
// 保存自定义 init 方法的调用
- (void)forwardInvocation:(NSInvocation *)invocation {
    _initInvocation = invocation;
}

先还是理解如下的内容

NSMethodSignature&NSInvocation

以下内容来自iOS 使用NSMethodSignature和 NSInvocation进行 method 或 block的调用

iOS中用三种方式调用方法:
如下的printStr1方法

- (void)printStr:(NSString*)str{
    NSLog(@"printStr  %@",str);
}

1.直接调用

[self printStr:@"hello world 1"];

2.使用performSelector:withObject:

[self performSelector:@selector(printStr:) withObject:@"hello world 2"];

3.使用NSMethodSignature&NSInvocation

    //获取方法签名
    NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr:)];
    //获取方法签名对应的invocation
    NSInvocation *invocationOfPrintStr = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
    /**
     设置消息接受者,与[invocationOfPrintStr setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等价
     */
    [invocationOfPrintStr setTarget:self];
    /**设置要执行的selector。与[invocationOfPrintStr setArgument:@selector(printStr1:) atIndex:1] 等价*/
    [invocationOfPrintStr setSelector:@selector(printStr:)];
    //设置参数
    NSString *str = @"hello world 3";
    [invocationOfPrintStr setArgument:&str atIndex:2];
    //开始执行
    [invocationOfPrintStr invoke];

NSMethodSignature

NSMethodSignature用于描述method的类型信息:返回值类型,及每个参数的类型。

使用NSMethodSignature对象转发接收对象(receiving object)不响应的消息。

创建NSMethodSignature:

1.使用NSObjectmethodSignatureForSelector:创建NSMethodSignature对象
2.使用signatureWithObjCTypes:方法

[NSMethodSignature signatureWithObjCTypes:"v@:"];

一般使用NSMethodSignature对象来创建NSInvocation对象,传递给forwardInvocation:方法

一个方法的签名由多个字符组成,首先是返回值类型,接着是隐式参数self_cmd的字符编码,接着是一个或多个显示参数。

NSInvocation

NSInvocation作为对象呈现的Objective-C消息。(An Objective-C message rendered as an object.)

NSInvocation对象主要用于存储和转发对象之间和应用程序之间的消息,主要是NSTimer对象和分布式对象系统。NSInvocation对象包含Objective-C消息的所有元素:目标(target),选择器(selector),参数和返回值。可以直接设置这些元素,并在NSInvocation对象被dispatched时自动设置返回值。

使用invocationWithMethodSignature:方法创建NSInvocation对象,不要使用allocinit来创建

Objective-C消息转发

内容来自轻松学习之 Objective-C消息转发

如果调用一个没有实现的方法,首先,该方法在调用时,系统会查看这个对象能否接收这个消息(查看这个类有没有这个方法,或者有没有实现这个方法。),如果不能并且只在不能的情况下,就会调用下面这几个方法,给你“补救”的机会,你可以先理解为几套防止程序crash的备选方案,我们就是利用这几个方案进行消息转发,注意一点,前一套方案实现后一套方法就不会执行。如果这几套方案你都没有做处理,那么程序就会报错crash。

这几套方法如下:
1.自己为这个方法增加实现。

  • + (BOOL)resolveInstanceMethod:(SEL)sel实例方法
  • + (BOOL)resolveClassMethod:(SEL)sel类方法
void run(id self, SEL _cmd)
{
    NSLog(@"%@ %s", self, sel_getName(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(run)) {
        class_addMethod(self, sel, (IMP)run, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

2.转发消息,让别的类处理,- (id)forwardingTargetForSelector:(SEL)aSelector返回需要转发消息的对象

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return [[Car alloc] init];
}

3.如果我们不实现forwardingTargetForSelector,系统就会调用方案三的两个方法methodSignatureForSelectorforwardInvocation

需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *sel = NSStringFromSelector(aSelector);
    if ([sel isEqualToString:@"run"]) {
        //为转发方法手动生成签名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector = [anInvocation selector];
    //创建需要转发消息的对象
    Car *car = [[Car alloc] init];
    if ([car respondsToSelector:selector]) {
        //唤醒这个方法
        [anInvocation invokeWithTarget:car];
    }
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐