NSTimer的属性
+ @property(readonly, getter=isValid) BOOL valid :
返回Boolean 表示当前的timer是否还有效。
+ @property(copy) NSDate *fireDate :
定时器的触发时间。如果定时器已经无效,则返回最后一次的启动时间。也可以通过该属性来改变定时器的触发时间。
+ @property(readonly) NSTimeInterval timeInterval :
返回定时器的时间间隔。如果定时器的repeat为NO,则返回0。
+ @property(readonly, retain) id userInfo :
返回定时器的userInfo 对象,如果定时器已经失效,则无权访问,所以用之前,先通过valid 来检测定时器是否有效。
NSTimer的方法
类方法
1、以scheduled(安排)开头的方法,该类型方法创建的定时器,已经将定时器以默认的运行模式(NSDefaultRunLoopMode)安排到当前到run loop 中。即,表示不需要下面的方法手动将定时器加到run loop中。
- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;
- 当创建后,repeat设为NO,则会在当前的NSDate,延迟interval后,执行一次。但是调用fire方法,则会立马执行。
- repeat 设为YES,则每间隔interval,执行一次。
//interval表示时间间隔,
//repeats 表示是否重复执行,
//block中是定时器的执行代码。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
//target 表示当前定时器启动时,接受aSelecttor消息的对象,并且定时器会对target 强引用,
//selector 表示定时器启动时发送给target的消息,
//userInfo 表示定时器的用户信息,同样会强引用,一般为nil,
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
//invocation 是NSInvocation类型,当定时启动时,通过该对象进行消息转发(调用某个对象的消息),同样也是强引用。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
2、需要手动加到runloop中,如果不加,不执行;调用fire方法则会执行一次,无论repeat是否为ture。
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
实例方法
1、创建定时器,需要手动加到run loop中。
//date:定时器启动的时间。
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep;
2、
- (void)fire:启动定时器的方法。
- (void)invalidate :销毁定时器。
总结
- 有block 的方法都是iOS10++ 才有,并且不存在循环引用的问题(已验证)。
- 以scheduled 开头的方法,不需要手动将定时器加到runloop中,其他方法都需要。
- 如果不加到run loop ,无论repeat是否为true ,都不会执行;调用fire ,只会执行一次,无内存泄漏的问题。
NSTimer的循环引用问题的本质是:
NSTimer在初始化的时候是放在VC方法中的,而VC的self又是作为NSTimer对象的一个参数存在的,就导致了一个死循环。
解决问题的本质:打破NSTimer对当前View的持有。
解决问题的方法:
1、调用invalidate方法,销毁定时器。切记:不可在VC的delloc方法中调用,循环引用不会走到delloc。
2、通过代理弱引用:
- 创建middleWeak类,定义protocol,及弱引用delegate,定义timeAction方法,并在该方法中调用代理的方法;
- 目标VC中,实现该代理,创建NStimer实例时,将target设为middleWeak,selector为timeAction。
- 过程:定时器到执行middleWeak中的timeAction,然后回调目标VC的代理方法。
3、利用NSProxy消息转发
- 创建middleNSProxy,实现methodSignatureForSelector 和 forwardInvocation,并创建weak修饰的id类型的target
- 创建middleNSProxy实例,并将proxy的target设为self。
- 将time的target设为middleNSproxy。
@interface ceshiProxy : NSProxy
@property (nonatomic, weak) id aTarget; // 此对象要从外部传过来
@end
//.m
#import "ceshiProxy.h"
@implementation ceshiProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.aTarget methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.aTarget];
}
@end
//使用
ceshiProxy *proxy = [ceshiProxy alloc];
proxy.aTarget = self;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:proxy selector:@selector(timerFire) userInfo:nil repeats:YES];
4、利用新的API
在iOS10以后,提供了三种新的API
scheduledTimerWithTimeInterval:repeats:block:
timerWithTimeInterval:repeats:block:
initWithFireDate:interval:repeats:block:
解决列表滑动,Timer不执行的问题:
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
原因:当滑动ScrollView的时候,NSRunloop的mode并不是NSDefaultRunLoopMode,而是UITrackingRunLoopMode,为此,我们需要设置一个包含既包含NSDefaultRunLoopMode又包含UITrackingRunLoopMode的mode,那就是NSRunLoopCommonModes。
网友评论