美文网首页
iOS 处理定时任务常用方法

iOS 处理定时任务常用方法

作者: 大成小栈 | 来源:发表于2019-06-18 17:23 被阅读0次

    在项目开发中,经常会在代码中处理一些需要延时或定时执行的任务,iOS 中处理定时任务的方法包括 performSelector 方法、NSTimer、GCD、CADisplayLink,其本质都是通过RunLoop来实现,下面我们就对这几个方法做一些总结。

    1. performSelector方法

    在NSRunLoop.h中有对NSObject类的扩展方法,简单易用:

    @interface NSObject (NSDelayedPerforming)
    
    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
    
    + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
    + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
    
    @end
    
    2. NSTimer

    NSTimer 是最常使用的定时器,使用方式简单,NSTimer 是也通过添加到RunLoop中被触发并进行工作的,桥接 CFRunLoopTimerRef。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));
    
    - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
    

    以下是初始化NSTimer的不同方式:

    // 自动加入currentRunLoop
    self.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
    //self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0 repeats:YES block:^(NSTimer * _Nonnull timer) { }];
    
    // 手动加入RunLoop
    self.timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    
    // 指定timer触发时刻
    NSTimeInterval timeInterval = [self timeIntervalSinceReferenceDate] + 30;
    NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:timeInterval];
    self.timer = [[NSTimer alloc] initWithFireDate:newDate interval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    

    如果当前界面中有UITableView,则在 UITableView 在滚动过程中,上述代码中的定时器到了时间并没有触发。根据RunLoop的相关知识,同一时刻 RunLoop 只运行在一种 Mode 上,并且只有这个 Mode 相关联的源或定时器会被传递消息,mainRunLoop 一般处于 NSDefaultRunLoopMode,但是在滚动或者点击事件等触发时,mainRunLoop 切换至 NSEventTrackingRunLoopMode ,而上面 timer 被加入的正是 NSDefaultRunLoopMode (未指明也默认加入默认模式),所以滑动时未触发定时操作。
    解决方法:添加timer到mainRunLoop的NSRunLoopCommonMode中或者子线程中,需要注意的是加入子线程时要手动开启并运行子线程的RunLoop。

    self.timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(timerRuning) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    

    NSRunLoopCommonMode这是一组可配置的常用模式。将输入源与此模式相关联也会将其与组中的每个模式相关联。对于Cocoa应用程序,此集合默认包括NSDefaultRunLoopMode,NSPanelRunLoopMode和NSEventTrackingRunLoopMode。

    注意:

    1. iOS10以前初始化的timer在运行期间会对target进行持有,因此,在释放时需要手动调用invalidate方法,并置nil;
    2. timer不能在当前宿主的dealloc方法中调用,因为timer没有被释放前,当前宿主不会执行dealloc方法;
    3. 当前RunLoop会切换Mode,因此可能导致timer不是立刻被触发。
    4. 在同一线程中,timer重复执行期间,有其他耗时任务时,在改耗时任务完成前也不会触发定时,在耗时任务完成后,timer的定时任务会继续执行。
    5. dispatch_source_set_timer中设置启动时间,dispatch_time_t可通过两个方法生成:dispatch_time 和 dispatch_walltime
    3. GCD定时器

    我们也可以通过GCD中的方法实现定时器来处理定时任务,实现的代码逻辑如下:

    // 1. 创建 dispatch source,指定检测事件为定时
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue("Timer_Queue", 0));
    // 2. 设置定时器启动时间、间隔
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC,  0 * NSEC_PER_SEC); 
    // 3. 设置callback
    dispatch_source_set_event_handler(timer, ^{
            NSLog(@"timer fired");
        });
    dispatch_source_set_event_handler(timer, ^{
           //取消定时器时一些操作
        });
    // 4. 启动定时器(刚创建的source处于被挂起状态)
    dispatch_resume(timer);
    // 5. 暂停定时器
    dispatch_suspend(timer);
    // 6. 取消定时器
    dispatch_source_cancel(timer);
    timer = nil;
    

    当我们想要timer只是延时执行一次时,只调用以下方法即可:

    // 在主线程中延时5s中执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
        });
    

    注意:

    1. 正在执行的 block,在调用dispatch_suspend(timer)时,当前block并不会立即停止而是继续执行至完成;
    2. dispatch source在挂起时,直接设置为 nil 或者重新赋值都会造成crash,需要在activate的状态下调用dispatch_source_cancel(timer)后置为 nil 或者重新赋值;
    3. dispatch_source_cancel方法可以在dispatch_source_set_event_handler中调用,即timer可内部持有也可外部持有;
    4. dispatch_resume和dispatch_suspend调用需成对出现,否则会crash;
    5. dispatch source会比 NSTimer 更精准一些。

    参考文章

    相关文章

      网友评论

          本文标题:iOS 处理定时任务常用方法

          本文链接:https://www.haomeiwen.com/subject/ofcfqctx.html