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,视图消失的时候,我们调用Timer 的invalidate,让RunLoop释放对Timer的持有,响应的Timer也就释放对VC的持有
缺点:当我们在自定义View的视图中也添加Timer定期器任务,我们要做什么时候释放呢?也许,可以再Timer属性暴露给VC使用,这样的代码耦合度比较高,不建议使用。
方法二
显然这种方式是不能打破,放回上一级,VC释放掉。
@property (nonatomic, weak) NSTimer *timer;
高逼格方法:NSProxy
从右边入手,如果我们能让Timer持有VC变为弱引用的话,那么当返回上一级的时候就不会出现这种情况。
NSProxy的Apple文档说明,简单来说提供了几个信息
- NSProxy是一个专门用来做消息转发的类
- NSProxy是个抽象类,使用需自己写一个子类继承自NSProxy
- NSProxy的子类需要实现两个方法methodSignatureForSelector 和 forwardInvocation
我们知道了解了OC中消息转发的机制,那么你肯定知道,当某个对象的方法找不到的时候,也就是最后抛出doesNotRecognizeSelector的时候
- 消息发送,从方法缓存中找方法,找不到去方法列表中找,找到了将该方法加入方法缓存,还是找不到,去父类里重复前面的步骤,如果找到底都找不到那么进入2。
- 动态方法解析,看该类是否实现了resolveInstanceMethod:和resolveClassMethod:,如果实现了就解析动态添加的方法,并调用该方法,如果没有实现进入3。
- 消息转发,这里分二步
- 调用forwardingTargetForSelector:,看返回的对象是否为nil,如果不为nil,调用objc_msgSend传入对象和SEL。
- 如果上面为nil,那么就调用methodSignatureForSelector:返回方法签名,如果方法签名不为nil,调用forwardInvocation:来执行该方法
- 如果以上都没有处理: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];
}
引用流程图: