NSTimer
@interface TwoViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) UILabel *textLabel;
@end
- (void)testTimer1 {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[timer fire];
_timer = timer;
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
上面造成的结果是 dealloc 永远无法执行,timer不会被释放,有人会说把属性中的timer 改成弱引用,测试后还是不行,原因是什么呢? 我们发现UIViewController 无论是否强弱引用着 timer 都会造成, timer都会强引用者UIViewController。核心原因是因为下面这个引用链。
NSRunLoop --->NSTimer ---> UIViewController
无论UIViewController 是否强引用者timer,我们发现上面的引用链都会存在,一般我们都是在主线程用的NSTimer。
如何解决呢?
- 使用不带target的 block 防止出现控制器无法释放的问题,使用target会导致这个问题,那我不使用target这个api问题不解决了吗?代码如下
__weak typeof(self) weakSelf = self;
NSTimer *timer2 = [NSTimer timerWithTimeInterval:5 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"我是timer2");
[weakSelf updateTime];
}];
//需要主动加入 runLoop 中
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSRunLoopCommonModes];
[timer2 fire];
_timer = timer2;
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
2.使用target,不让target直接引用这控制器,其中这个里面有两种处理方式:
a. 使用NSProxy来解决这个问题
@interface TTRealProxy : NSProxy
+ (TTRealProxy *)proxyWithTarget:(id)target;
@end
@interface TTRealProxy ()
@property (nonatomic, weak) id target;
@end
@implementation TTRealProxy
+ (TTRealProxy *)proxyWithTarget:(id)target {
TTRealProxy *realProxy = [TTRealProxy alloc];
realProxy.target = target;
return realProxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:_target];
}
@end
具体使用如下:
NSTimer *timer3 = [NSTimer scheduledTimerWithTimeInterval:1 target:[TTRealProxy proxyWithTarget:self] selector:@selector(updateTime) userInfo:nil repeats:YES];
[timer3 fire];
_timer = timer3;
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
b.使用类对象来充当target,其实这个最终处理的写法最终有点像 1中的解决方案了
@interface NSTimer (LWZAddtion)
+ (NSTimer *)LWZ_TimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer * _Nonnull))block;
- (void)lwz_fire;
@end
#import "NSTimer+LWZAddtion.h"
#import <objc/message.h>
@implementation NSTimer (LWZAddtion)
+ (void)lwz_PerformBlockWithTimer:(NSTimer *)timer {
if(timer.lwz_usingBlock != nil) timer.lwz_usingBlock(timer);
}
+ (NSTimer *)LWZ_TimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer * _Nonnull))block {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(lwz_PerformBlockWithTimer:) userInfo:nil repeats:repeats];
[timer set_LwzUsingBlock:block];
return timer;
}
- (void)set_LwzUsingBlock:(void (^)(NSTimer * _Nonnull))usingBlock {
objc_setAssociatedObject(self, @selector(lwz_usingBlock), usingBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void (^)(NSTimer * _Nonnull))lwz_usingBlock {
return objc_getAssociatedObject(self, _cmd);
}
- (void)lwz_fire {
[self setFireDate:[NSDate dateWithTimeIntervalSinceNow:self.timeInterval]];
}
真正的使用
- (void)testTimer4 {
__weak typeof(self) weakSelf = self;
NSTimer *timer4 = [NSTimer LWZ_TimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) {
[strongSelf.timer invalidate];
return;
}
[strongSelf updateTime];
}];
[timer4 lwz_fire];
_timer = timer4;
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
CADisplayLink
CADisplayLink 的问题和 NSTimer 产生的循环引用的问题原因是一样的,解决方案也类似
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[TTRealProxy proxyWithTarget:self] selector:@selector(updateTime)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
if(@available(ios 10.0 ,*)){
link.preferredFramesPerSecond = 1;
}else{
link.frameInterval = 60;
}
_link = link;
- (void)dealloc {
[self.link invalidate];
self.link = nil;
}
GCD定时器:当我们需要定时器非常准的时候,可以用这个,前两个和runloop有关系,不论怎么样都可能不准。
代码如下:
@interface TGCDTimer : NSObject
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat :(BOOL)repeat queue:(dispatch_queue_t) queue block:(dispatch_block_t) block;
- (void)stop;
- (void)restart;
- (void)invalidate;
@end
#import "TGCDTimer.h"
@interface TGCDTimer() {
dispatch_source_t _timer;
BOOL _isFire;
}
@end
@implementation TGCDTimer
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval repeat :(BOOL)repeat queue:(dispatch_queue_t) queue block:(dispatch_block_t) block {
//队列必须存在
if(queue == NULL) return nil;
if(self = [super init]) {
// 1.创建定时器
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 2.设置定时器的时间
dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
// 3.监听定时器的回调
dispatch_source_set_event_handler(_timer, ^{
//执行Block
if(block) {
block();
}
//如果不重复,执行一次后销毁
if (!repeat) {
self->_isFire = NO;
dispatch_source_cancel(self->_timer);
}
});
//在相等的间隔后调起 timer
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
dispatch_resume(self -> _timer);
self->_isFire = YES;
});
}
return self;
}
- (void)stop {
if(_isFire){
_isFire = NO;
dispatch_suspend(self->_timer);
}
}
- (void)restart {
if (!_isFire) {
_isFire = YES;
dispatch_resume(self->_timer);
}
}
- (void)invalidate {
_isFire = NO;
dispatch_source_cancel(self->_timer);
}
- (void)dealloc {
_isFire = NO;
dispatch_cancel(self->_timer);
}
@end
具体使用
- (void)testTimer5 {
__weak typeof(self) weakSelf = self;
TGCDTimer *timer = [[TGCDTimer alloc]initWithTimeInterval:1 repeat:YES queue:dispatch_get_main_queue() block:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) {
[strongSelf.gcdTimer invalidate];
}
NSLog(@"我是timer5");
[strongSelf updateTime];
[strongSelf freez];
}];
self.gcdTimer = timer;
}
- (void)dealloc {
//停止GCD 定时器
[self.gcdTimer invalidate];
}