我们在开发应用的过程中,往往在很多地方需要倒计时,比如说轮播图,验证码,活动倒计时等等。而在实现这些功能的时候,我们往往会遇到很多坑需要我们小心的规避掉。
因为文章内容的关系,要求大家都有一些runloop的基础知识,当然如果没有,也没什么特别大的问题。这里推荐一下 ibireme的这篇文章。
话不多说,直接上正题:
倒计时的种类
在开发过程中,我们基本上只用了这几种方式来实现倒计时
1.PerformSelecter
2.NSTimer
3.CADisplayLink
4.GCD
PerformSelecter
我们使用下面的代码可以实现指定延迟之后执行:
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
它的方法描述如下
Invokes a method of the receiver on the current thread using the default mode after a delay.
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
If you want the message to be dequeued when the run loop is in a mode other than the default mode, use the performSelector:withObject:afterDelay:inModes: method instead. If you are not sure whether the current thread is the main thread, you can use the performSelectorOnMainThread:withObject:waitUntilDone: or performSelectorOnMainThread:withObject:waitUntilDone:modes:method to guarantee that your selector executes on the main thread. To cancel a queued message, use the cancelPreviousPerformRequestsWithTarget: or cancelPreviousPerformRequestsWithTarget:selector:object:method.
这个方法在Foundation框架下的NSRunLoop.h文件下。当我们调用NSObject 这个方法的时候,在runloop的内部是会创建一个Timer并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。而且还有几个很大的缺陷:
这个方法必须在NSDefaultRunLoopMode下才能运行
因为它基于RunLoop实现,所以可能会造成精确度上的问题。
这个问题在其他两个方法上也会出现,所以我们下面细说内存管理上非常容易出问题。
当我们执行 [self performSelector: afterDelay:]的时候,系统会将self的引用计数加1,执行完这个方法时,还会将self的引用计数减1,当方法还没有执行的时候,要返回父视图释放当前视图的时候,self的计数没有减少到0,而导致无法调用dealloc方法,出现了内存泄露。
因为它有如此之多的缺陷,所以我们不应该使用它,或者说,不应该在倒计时这方法使用它。
NSTimer
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(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 *)scheduledTimerWithTimeInterval:(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 API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
方法描述如下
A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
To use a timer effectively, you should be aware of how run loops operate. See Threading Programming Guide for more information.
A timer is not a real-time mechanism. If a timer’s firing time occ