iOS NSTimer 循环引用

NSTimer

我们常常用NSTimer做一些定时任务,代码如下:

#import "YTimerVC.h"

@interface YTimerVC ()
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation YTimerVC

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeAction) userInfo:nil repeats:true];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timeAction {
    static int i = 0;
    NSLog(@"%d", i++);
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}


问题:当我们返回上个页面的时候,返现dealloc方法并没有走。造成ViewController内存泄漏。
循环引用图
在这里插入图片描述
当我们放回上个页面的时候,由于Timer添加到RunLoop执行,Timer不会释放TimerVC的持有。

最简单的方法一

在ViewController,视图消失的时候,我们调用Timerinvalidate,让RunLoop释放对Timer的持有,响应的Timer也就释放对VC的持有

缺点:当我们在自定义View的视图中也添加Timer定期器任务,我们要做什么时候释放呢?也许,可以再Timer属性暴露给VC使用,这样的代码耦合度比较高,不建议使用。

方法二

显然这种方式是不能打破,放回上一级,VC释放掉。

@property (nonatomic, weak) NSTimer *timer;

在这里插入图片描述

高逼格方法:NSProxy

从右边入手,如果我们能让Timer持有VC变为弱引用的话,那么当返回上一级的时候就不会出现这种情况。
NSProxyApple文档说明,简单来说提供了几个信息

  1. NSProxy是一个专门用来做消息转发的类
  2. NSProxy是个抽象类,使用需自己写一个子类继承自NSProxy
  3. NSProxy的子类需要实现两个方法methodSignatureForSelectorforwardInvocation

我们知道了解了OC中消息转发的机制,那么你肯定知道,当某个对象的方法找不到的时候,也就是最后抛出doesNotRecognizeSelector的时候

  1. 消息发送,从方法缓存中找方法,找不到去方法列表中找,找到了将该方法加入方法缓存,还是找不到,去父类里重复前面的步骤,如果找到底都找不到那么进入2。

  2. 动态方法解析,看该类是否实现了resolveInstanceMethod:和resolveClassMethod:,如果实现了就解析动态添加的方法,并调用该方法,如果没有实现进入3。

  3. 消息转发,这里分二步

    1. 调用forwardingTargetForSelector:,看返回的对象是否为nil,如果不为nil,调用objc_msgSend传入对象和SEL
    2. 如果上面为nil,那么就调用methodSignatureForSelector:返回方法签名,如果方法签名不为nil,调用forwardInvocation:来执行该方法
  4. 如果以上都没有处理:doesNotRecognizeSelector

NSProxy代码实现:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface YProxy : NSProxy
@property (nonatomic, weak) id target;
@end

NS_ASSUME_NONNULL_END

#import "YProxy.h"
@implementation YProxy
/**
 objc_methodSend(id, SEL,...)
 
 流程:resolveInstanceMethod/resolveClassMethod
 如果没有响应:forwardingTargetForSelector //转发tagret,切记不能是self,会造成循环
 如果设置为Nil:methodSingnatureForSelector
 -> forwardInvocation
 
 */
///先走这个:
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}

///如果forwardingTargetForSelector 为空的话,我们就用NSOjbect初始化一个init
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
///如果声明一个null对象,并返回[invocation getReturnValue:&null]。让null去响应这样,就算我们不赋值taget 也不会出现崩溃现象
- (void)forwardInvocation:(NSInvocation *)invocation {
//    [invocation invokeWithTarget:self.target];
    void *null = NULL;
    [invocation getReturnValue:&null];
}
@end


//controller代码

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    YProxy *proxy = [YProxy alloc];
    proxy.target = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:proxy selector:@selector(timeAction) userInfo:nil repeats:true];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timeAction {
    static int i = 0;
    NSLog(@"%d", i++);
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

引用流程图:

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章