前言:在使用定时器地过程中,如果没有在合适的位置销毁定时器往往会导致定时器无法释放而发生内存泄漏,定时器也会持续消耗CPU资源,电量。所以需要一种能自动释放地定时器,方便开发使用。
消除内存泄漏主要有两种方法,一种是断环避免循环引用,一种是主动断开避免循环引用。这里的NSTimer将会用两种方法来解决循环引用地问题
首先分析一下NSTimer为啥会带来循环引用
1. NSTimer地支撑是Runloop。Runloop会强引用NSTimer,所以必须手动调用NSTimer的失效方法。
2. NSTimer会强引用所服务的对象,一般对象(VC)也会强引用定时器(弱引用同样不能释放)。
1.第一种解决方案:断环避免循环引用
知识贮备:1.weak属性不会造成强引用。2.iOS有两个基类,一个是NSObject,领一个就是今天用到的NSProxy,一个专门用来做消息转发的对象,因为他地效率更高(用NSObject也完成没有问题)
原理:添加一个中间对象,这个中间对象强引用被定时器强引用,而定时器被VC强应用,中间对象弱应用VC。这样VC就不会被循环引用,我们在VC地析构函数里面失效定时器。
需要新建一个中间对象,继承自NSProxy
.h文件
#import <Foundation/Foundation.h>
@interface YYEProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
.m文件,只做了一件是,就是消息转发
#import "YYEProxy.h"
@implementation YYEProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
YYEProxy *proxy = [YYEProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
使用方法,注意在dealloc里面失效定时器
#import "YYEProxy.h"
@interface SecViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation SecViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YYEProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
2.第二种解决方案:主动断开避免循环引用
知识贮备:1.weak属性在其指向对象销毁地时候的时候回自动置为nil。
原理:和第一中方法类似,这里也要引入中间对象,由中间对象弱引用对象(VC)。不同的是,VC可以不持有(也可以持有,看需求)。定时器强引用中间对象,中间对象弱(强也行)引用定时器,这样在对象被销毁地时候,weak指针为nil。在定时函数里面去自动地销毁定时器。
用分类实现 .h文件
#import <Foundation/Foundation.h>
@interface NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
.m文件
#import "NSTimer+WeakTimer.h"
@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
- (void)fire:(NSTimer *)timer;
@end
@implementation TimerWeakObject
- (void)fire:(NSTimer *)timer
{
if (self.target) {
if ([self.target respondsToSelector:self.selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:timer.userInfo];
#pragma clang diagnostic pop
}
}
else{
[self.timer invalidate];
}
}
@end
@implementation NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
{
TimerWeakObject *object = [[TimerWeakObject alloc] init];
object.target = aTarget;
object.selector = aSelector;
object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
return object.timer;
}
@end
使用方法
#import "NSTimer+WeakTimer.h"
@interface SecViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation SecViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledWeakTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
// 如果不点击屏幕,定时器也会随着控制器地销毁而销毁,具体使用看需求
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.timer invalidate];
self.timer = nil;
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
@end
最间用法
#import "NSTimer+WeakTimer.h"
@implementation SecViewController
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledWeakTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
@end
总结:第一种方法使用较为第二种方法麻烦,我推荐使用第二种方法,只要定时器随着对象地生命周期,就可以做到自动销毁。如果您集成之后发现有什么问题,欢迎与我一起探讨!