NSTimer
计时器要和runloop相关联,runloop会触发任务。创建NSTimer时,可以将其“预先安排”在当前的runloop中,也可以创建好,然后再由开发人员自己调度。无论采用什么方式,只要把计时器放在循环里,它才能创建好触发的任务
代码语言:javascript复制//将其预先安排在当前的runloop中
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(run) userInfo:nil repeats:YES];
//停止timer
[self.timer invalidate];
注:target和selector参数表示计时器将在哪个对象上调用哪个方法,同时,计算机会保留其目标对象,直到计时器失效才会放弃此对象。
NSTimer的循环引用
创建定时器时,Student引用了定时器,在给定时器添加任务时,定时器保留了目标对象self,这里就出现了循环引用
代码语言:javascript复制#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic,strong) NSTimer *timer;
- (void)start;
- (void)end;
@end
--------------------
#import "Student.h"
@implementation Student
- (void)start{
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
- (void)end{
[_timer invalidate];
_timer = nil;
}
- (void)run{
NSLog(@"--");
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
--------------------
#import "ViewController.h"
#import "Student.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [[Student alloc]init];
[stu start];
}
@end
解决这个循环引用的方法
- Student不再引用定时器
- 定时器不再保留目标对象self
Student不再引用定时器是不可行的,那么就只有第二种方法了。也就是合适的时候调用end方法。然而,合适的时机很难找到。假如这是一个验证码倒计时程序,你可以在倒计时结束时调用end方法。但是你不能确定用户一定会等倒计时结束才返回到上一级页面.或许你想在dealloc方法中使定时器失效,那你就太天真了。此时定时器还保留着当前控制器,此方法是不可能调用的,因此会出现内存泄漏。或许在倒计时程序中,你可以重写返回方法,先调用end再返回,但这不是一个好主意
这里采用block块的方法为NSTimer增加一个分类,具体细节看代码(程序员最好的语言是代码)。
代码语言:javascript复制#import <Foundation/Foundation.h>
@interface NSTimer (SSBlockSupport)
(NSTimer *)ss_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval
repeats:(BOOL)repeats
block:(void(^)(NSTimer *timer))block;
@end
----------
#import "NSTimer SSBlockSupport.h"
@implementation NSTimer (SSBlockSupport)
(NSTimer *)ss_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{
return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(ss_blcokInvoke:) userInfo:[block copy] repeats:repeats];
}
(void)ss_blcokInvoke:(NSTimer *)timer {
void (^block)(NSTimer *timer) = timer.userInfo;
if (block) {
block(timer);
}
}
@end
----------
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property(nonatomic, strong) NSTimer *timer;
- (void)start;
- (void)end;
@end
----------
#import "Student.h"
#import "NSTimer SSBlockSupport.h"
@implementation Student
- (void)start{
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer ss_scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer *timer) {
[weakSelf run];
}];
}
- (void)end{
[_timer invalidate];
_timer = nil;
}
- (void)run{
NSLog(@"--");
}
- (void)dealloc{
[_timer invalidate];
NSLog(@"%s",__func__);
}
@end
----------
#import "ViewController.h"
#import "Student.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *stu = [[Student alloc]init];
[stu start];
}
@end
在iOS10中,新的定时器即使不被引用,也可以正常运行,但是这依然会导致target不能释放,上面的方法依然十分有用。iOS10中,定时器的API新增了block方法,实现原理和这一样,只不过我这里用的是分类,而系统是在原始类中直接添加方法,最终的行为是一致的
代码语言:javascript复制#import "Student.h"
@implementation Student
- (void)start{
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf run];
}];
}
- (void)end{
[_timer invalidate];
_timer = nil;
}
- (void)run{
NSLog(@"--");
}
- (void)dealloc{
[_timer invalidate];
NSLog(@"%s",__func__);
}
@end