NSTimer的定义
A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
简单理解就是在一定的时间间隔后向指定对象发送指定消息。
NSTimer的调用方式
一、自动加入NSRunLoop
方法名以scheduled开头的均不需要手动加入NSRunLoop
方法有三个:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block
二、需要手动加入NSRunLoop
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block
重点参数解析
参数:target
The object to which to send the message specified by aSelector when the timer fires.
The timer maintains a strong reference to this object until it (the timer) is invalidated.
当timer开启时,消息的reciver。NSTimer会强引用target直到(the timer) invalidated.
参数:userInfo
Custom user info for the timer.
The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.
自定义的额外数据。和target一样,NSTimer会强引用userInfo直到(the timer) invalidated.
参数:repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
repeats为YES时,会重复执行selector方法直到timer invalidated为止。
repeats为NO时, timer会在fires后被置为invalidated。
注意
:以上说明,repeats为YES时,要想截止timer,需要调用[timer invalidate]方法,而repeats为NO时,系统自动在fires后调用[timer invalidate]方法。
- (void)invalidate;
Stops the timer from ever firing again and requests its removal from its run loop.
停止timer,并请求从其运行循环中删除它。
This method is the only way to remove a timer from an [NSRunLoop
](apple-reference-documentation://hclPs8uY7g) object. The NSRunLoop
object removes its strong reference to the timer, either just before the [invalidate
](apple-reference-documentation://hcnIKt1Jb9) method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.
该方法是从NSRunLoop对象中删除timer的唯一方法。 NSRunLoop对象删除其对定时器的强引用,就在invalidate方法返回之前或稍后一点。
如果配置了target和userInfo对象,timer也会删除其对这些对象的强引用。
常说的循环引用
1, self强引用timer对象。
即如下方式:
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerSelector) userInfo:nil repeats:YES];
对象引用图:
执行了invalidate方法后:timer--->target, runloop--->timer 之间的强引用被删除。也就不会再有循环引用了。(当repeats参数为NO时,timer 触发后,这两个强引用就自动被删除了)。
所以如果repeats为YES,并且没有执行invalidate方法时,形成了循环引用,这样self就无法释放,从而内存泄漏。(有同学在self的dealloc方法中进行[timer invalidate]调用是无效果的,因为dealloc方法根本不会执行。)
2,self没有强引用timer的情况
执行了invalidate方法后:timer--->target, runloop--->timer 之间的强引用被删除。也就不会再有循环引用了。(当repeats参数为NO时,timer 触发后,这两个强引用就自动被删除了)。
所以如果repeats为YES,并且没有执行invalidate方法时,虽然没有循环引用,但是runloop强引用timer,timer强引用self(target),由于(主线程中)runloop是程序运行期永久存在的,所以timer无法释放,self(target)也就无法释放,从而内存泄漏。
系统方法中有一个方法不会导致self(target)无法释放。如下:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block
该方法没有参数targe,所以不会强引用self(target), 所以self(target)可以释放,但是如果没有调用invalidate方法, 所创建的timer也是无法释放的,所以依旧会内存泄漏。
综上所诉,当参数repeats为YES时,必须在不使用timer的时候调用invalidate方法删除相关强引用,以免内存泄漏。
避免循环引用的方式:
1,使用另一个对象,timer强引用newObjc,newObjc弱引用target。
github上已经有了代码:YYWeakProxy
对象引用图:
这样timer就不再强引用self了,可以在self的dealloc中执行invalidate方法释放timer--->yyweakproxy, runloop----->timer的强引用.
原理:YYWeakProxy中weak属性target,并重写forwardingTargetForSelector方法,直接返回_target.
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
2,将target设置为类对象。
对象引用图
虽然依旧有强引用:timer--->NSTimer类对象,但是由于类对象不需要回收,所以没有内存泄漏问题。
实现方式:
@interface NSTimer (BlocksSupport)
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (BlocksSupport)
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+(void)blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
block();
}
}
@end