NSTimer Use
1. 初始化
a) [NSTimerscheduledTimerWithTimeInterval:1target:selfselector:@selector(timerClickMethod)userInfo:nilrepeats:YES];
b) [NSTimerscheduledTimerWithTimeInterval:1repeats:YESblock:^(NSTimer* _Nonnulltimer) {}];
c) NSMethodSignature *signature = [PSTimerStudyViewControllerinstanceMethodSignatureForSelector:@selector(run:)];
NSInvocation*invocation = [NSInvocationinvocationWithMethodSignature:signature];
//设置方法调用者
invocation.target= self;
//注意:这里的方法名一定要与方法签名类中的方法一致
invocation.selector= @selector(run:);
NSString*way =@"byCar";
//这里的Index要从2开始,以为0跟1已经被占据了,分别是self(target),selector(_cmd)
[invocation setArgument:&wayatIndex:2];
//3、调用invoke方法
[invocation invoke];
self.timer2= [NSTimerscheduledTimerWithTimeInterval:1invocation:invocationrepeats:YES];
以上三种初始化方法默认添加到NSRunLoop中,还有对应的三种方法需要手动添加到Runloop中
[[NSRunLoopcurrentRunLoop] addTimer:self.timerforMode:NSDefaultRunLoopMode];
+ (NSTimer*)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullableid)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer*) timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation*)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer*) timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(NSTimer*timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
2. NSTimer都干了什么
“A timer provides a way to perform a delayed action or a periodic action.The timer waits until a certain time interval has elapsed and then fires, sending a specified message to a specified object. ”
官方的意思是从现在倒未来的某个时刻执行一次或者多次指定的方法,怎么保证触发指定事件的时候该方法有效。方法就是将方法的接收者进行retain。一次性的和重复性的区别在于,一次性的调用完成以后会自己调用invalidate,重复的则将永远持有。
比如一个NSObject对象
- (instancetype)init {
if(self= [super init]) {
NSLog(@"实例%@初始化",self);
self.timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES]; }
returnself;}
- (void)dealloc {
[self.timerinvalidate];
self.timer= nil;
NSLog(@"实例%@被销毁了",self);
}
/*** 计时器触发的方法 */
-(void) timerMethod {
NSLog(@"计时器里的PSTimerObject%@",self);
}
在Controller中执行了如下方法
- (void) initTimerMethod {
self.timerObject= [[PSTimerObjectalloc] init];
__weaktypeof(self) weakSelf = self;
NSLog(@"当前类中的PSTimerObject%@",self.timerObject);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
weakSelf.timerObject= nil;
NSLog(@"当前类中的PSTimerObject%@",weakSelf.timerObject);
});
self.timer= [NSTimer scheduledTimerWithTimeInterval:2 target:selfselector:@selector(checkCurrentTimerObject) userInfo:nil repeats:YES];}
- (void) checkCurrentTimerObject {
NSLog(@"当前类计时器方法中的%@",self.timerObject);
}
通过输出结果得知,Object中的timer一直在周期性调用指定的方法,而Object对象也一直没有真正的被释放,因为对象被timer retain了,从而保证了调用方法的正确性,但由此引申出一个新的问题-内存管理,timer引用的对象会一直存在,很容易造成内存泄漏。
解决方法是:NSTimer提供了一个方法invalidate,不管是一次性的还是重复性的,执行完之后都会无效化(上方代码中的invalidate实际上是永远不会执行的(😜),思考一个问题:UITableViewCell中添加一个重复的timer,怎么实现invalidate方法),重复性的timer必须有对应的invalidate,否则很容易造成内存泄漏。
3. NSTimer是不是准时触发事件
不是,有时候差距比想象的大,NSTimer不是一个定时系统,不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟预想的有出入。出入大小跟当前程序执行的情况有关系,比如程序是多线程的,timer只是添加在某一个线程的RunLoop的某一个RunLoopMode 中,而多线程通常是分时执行,每次执行的mode也可能不一样。
假如添加了一个timer指定2秒后触发某一个事件,但刚好那时当前线程正在执行一个连续运算,timer此时就会延迟到运算执行完后才执行。延迟超过了一个周期,会和后面的触发进行合并,即一个周期只会触发一次。
比如
- (void) isTimerExact {
self.timer= [NSTimer scheduledTimerWithTimeInterval:1 target:selfselector:@selector(exactClick) userInfo:nil repeats:YES];
NSLog(@"内存要开始紧张!");
[self performSelector:@selector(busyMemory) withObject:nil afterDelay:5];
}
- (void) busyMemory {
NSLog(@"内存开始紧张!");
NSUIntegercaculateCount = 0x0FFFFFFFF;
CGFloatuselessValue = 0;
for(NSUInteger i = 0; i < caculateCount; ++i) {
uselessValue = i / 0.3333;
}
NSLog(@"内存紧张结束!");
}
- (void) exactClick {
NSLog(@"timer输出bibibibibbi");
}
输出结果可以发现,线程空闲的时候还是很准确,但是12.09开始一直忙着做大量运算,直到12.26才结束,线程忙完之后的触发时间与指定的时间还是一样的,timer不会因为触发延迟而导致后面的触发时间延迟。
SO timer 不是一种实时机制,会存在延迟,程度与当前线程执行的情况有关
4. NSTimer为什么要添加到RunLoop中才有作用
NSTimer其实也是一种资源,所有的资源要起作用,就得加到Runloop中,同理NSTimer 要起作用,也要加到RunLoop中,才会生效。
- (void) runloop {
NSLog(@"start");
self.timer=[[NSTimeralloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:self selector:@selector(runloopAct) userInfo:nil repeats:NO];
NSLog(@"end");
}
- (void) runloopAct {
NSLog(@"runloop");
}
2018-04-12 18:23:59.390298+0800 PSOCStudy[18788:807908] start
2018-04-12 18:23:59.390604+0800 PSOCStudy[18788:807908] end
RunloopAct永远不会触发。因为没有添加到RunLoop中
5. 添加到RunLoop中,但是不触发事件
a) RunLoop是否运行,每个线程都有自己的RunLoop,程序主线程会自动使RunLoop生效,但是我们自己新建的线程,RunLoop不会自己运行起来,想要使用,就得自己启动。
- (void) wrongRunloop {
if(@available(iOS 10.0, *)) {
[NSThread detachNewThreadWithBlock:^{
NSLog(@"start");
self.timer=[[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:self selector:@selector(runloopAct) userInfo:nil repeats:NO];
NSLog(@"end");
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}];
} else{
// Fallback on earlier versions
}
}
- (void) runloopAct {
NSLog(@"runloop");
}
2018-04-12 18:39:35.686356+0800 PSOCStudy[19043:832667] start
2018-04-12 18:39:35.686536+0800 PSOCStudy[19043:832667] end
b) Mode 是否正确。
比如一个控制器中有一个UITableView ,和一个NStimer ,UITableView滑动没有停止的时候的时候,self.timer好像不会执行,因为系统为了更好的处理UI和滚动事件,RunLoopModel 是处在 UITrackingRunLoopMode模式,而NSTimer默认是NSDefaultRunLoopMode模式,同一线程RunLoop在运行时,任意时刻只能处于一种mode,不处在同一Mode下,NStimer自然不会触发事件。可以通过改变NSTimer的RunLoopMode来使timer在UITableView滚动的时候timer也可以执行。
SO : 要让NSTImer生效,必须保证该线程的RunLoop已启动,而且RunLoopMode也要匹配