为什么要在非主线程创建NSTimer

  • 将 timer 添加到主线程的Runloop里面本身会增加线程负荷
  • 如果主线程因为某些原因阻塞卡顿了,timer 定时任务触发的时间精度肯定也会受到影响
  • 有些定时任务不是UI相关的,本来就没必要在主线程执行,给主线程增加不必要的负担。当然也可以在定时任务执行时,手动将任务指派到非主线程上,但这也是有额外开销的。

NSTimer的重要特性

  • NSTimer上的定时任务是在创建NSTimer的线程上执行的。NSTimer的销毁和创建必须在同一个线程上操作
  • NSTimer要被添加到当前线程的 Runloop 里面且 Runloop 被启动,定时任务(selector或者invocation)才会触发。

如何创建NSTimer对象

多数情况下,如此一行代码创建的NSTimer就能正常工作:

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFire) userInfo:nil repeats:YES]

因为这段创建代码是在主线程里面执行的,主线程里面会有系统创建好了的且已经启动了的 Runloop :[NSRunLoop mainRunLoop]。通过[NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:]创建时,会自动将创建的NSTimer对象加到当前的 Runloop 里面,所以 timer 能够创建后立马就能工作。

根据以上,可以这么创建自定义线程和运行在上面的 timer :

创建线程对象和NSTimer对象,定义函数

  • 子类化NSThread为Mythread,重载了dealloc和exit函数,在里面加了 log 输出,方便跟踪执行过程
    • Mythread.h文件为默认,略去
    • Mythread.m文件:
 #import "Mythread.h"
@implementation Mythread
- (void)dealloc
{
NSLog(@"Thread:%p dealloc",self);
}
+ (void)exit
{
NSLog(@"Thread:%p exit",self);
// 注意这是个类函数
[super exit];
}
@end
  • 创建NSThread和NSTimer对象
 @property (nonatomic , strong) NSThread *timerThread;

 @property (nonatomic , strong) NSTimer *timer;
  • 定义设置NSTimer的函数
 - (void)createTimer

 {

     self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFire) userInfo:nil repeats:YES];

     // 创建的线程中,runloop是不会自己启动的,需要手动启动

     [[NSRunLoop currentRunLoop] run];

     NSLog(@"createTimer,currentThread:%@,isMainThread:%@",[NSThread currentThread],@([NSThread isMainThread]));

 }
  • 定义self.timer的定时任务函数
 - (void)timerFire

 {

     static NSInteger counter = ;

     NSLog(@"%@,main:%@,counter:%@",[NSThread currentThread],@([NSThread isMainThread]),@(counter++));

 }  
  • 定义销毁self.timer的函数
 - (void)destoryTimerAndThread

 {

     NSLog(@"destoryTimerAndThread,currentThread:%@,isMainThread:%@",[NSThread currentThread],@([NSThread isMainThread]));

     [self.timer invalidate];

     @autoreleasepool {

         self.timerThread = nil;

         self.timer = nil;

     }

     // 释放打开的资源和清空申请的内存后,才可以退出,不然就会内存泄露

     [Mythread exit];

 }
  • 定义启动新线程的函数
 - (void)createAndStartThread

 {

     NSLog(@"createAndStartThread,currentThread:%@,isMainThread:%@",[NSThread currentThread],@([NSThread isMainThread]));

     self.timerThread = [[NSThread alloc] initWithTarget:self selector:@selector(createTimer) object:nil];

     [self.timerThread start];

 }
  • 定义销毁新线程的函数
 - (void)destoryThread

 {

     [self performSelector:@selector(destoryTimer) onThread:self.timerThread withObject:nil waitUntilDone:NO];

 }

调用过程

  1. 主线程中调用createAndStartThread启动线程和NSTimer
  2. 隔一小会,主线程中调用destoryThread销毁NSTimer和线程
  3. 隔一小会,主线程中调用createAndStartThread启动
  4. 隔一小会,主线程中调用destoryThread销毁NSTimer和线程

console输出结果

 ::07.166 : createAndStartThread,currentThread:<NSThread: 0x127d0b750>{number = , name = main},isMainThread:

 ::08.171 : timerFire,currentThread:<Mythread: 0x127d05810>{number = , name = (null)},isMainThread:,counter:

 ::09.173 : timerFire,currentThread:<Mythread: 0x127d05810>{number = , name = (null)},isMainThread:,counter:

 ::10.174 : timerFire,currentThread:<Mythread: 0x127d05810>{number = , name = (null)},isMainThread:,counter:

 ::11.174 : timerFire,currentThread:<Mythread: 0x127d05810>{number = , name = (null)},isMainThread:,counter:

 ::11.479 : destoryTimerAndThread,currentThread:<Mythread: 0x127d05810>{number = , name = (null)},isMainThread:

 ::11.481 : Thread:0x100011158 exit

 ::11.482 : Thread:0x127d05810 dealloc

 ::16.113 : createAndStartThread,currentThread:<NSThread: 0x127d0b750>{number = , name = main},isMainThread:

 ::17.124 : timerFire,currentThread:<Mythread: 0x127d21700>{number = , name = (null)},isMainThread:,counter:

 ::18.124 : timerFire,currentThread:<Mythread: 0x127d21700>{number = , name = (null)},isMainThread:,counter:

 ::19.124 : timerFire,currentThread:<Mythread: 0x127d21700>{number = , name = (null)},isMainThread:,counter:

 ::20.126 : timerFire,currentThread:<Mythread: 0x127d21700>{number = , name = (null)},isMainThread:,counter:

 ::21.126 : timerFire,currentThread:<Mythread: 0x127d21700>{number = , name = (null)},isMainThread:,counter:

 ::21.382 : destoryTimerAndThread,currentThread:<Mythread: 0x127d21700>{number = , name = (null)},isMainThread:

 ::21.383 : Thread:0x100011158 exit

 ::21.385 : Thread:0x127d21700 dealloc

示例说明

    • 为了节省显示空间,删除了部分 log 头信息
    • 创建和销毁时不一定要在主线程里面调用,只是为了方便比对输出结果
    • 在销毁 Timer 时,也不一定就要销毁线程,这里只是演示非主线程的创建和销毁

在非主线程里面使用NSTimer创建和取消定时任务的更多相关文章

  1. ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因

    引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ...

  2. 在非主线程中更新UI

    在非主线程中调用了showMessage方法,结果报错:Can't create handler inside thread that has not called Looper.prepare() ...

  3. Android 在非主线程无法操作UI意识

    Android在应用显示Dialog是一个非常easy事儿,但我从来没有尝试过Service里面展示Dialog. 经验UI操作要在主线程,本地的服务Service是主线程里没错,可是远程servic ...

  4. 后台子线程(非主线程)更新UI引起的警告

    一.问题描述 -(void)sendAsynchronousRequest { NSLog(@"%@",[NSThread currentThread]); [SVProgress ...

  5. Handler详解系列(四)——利用Handler在主线程与子线程之间互发消息,handler详解

    MainActivity如下: package cc.c; import android.app.Activity; import android.os.Bundle; import android. ...

  6. Android关于主线程和非主线程

    必须在主线程执行的任务: (1)UI更新 必须在非主线程中执行的任务 (1)Http请求 如执行:ImageHelper.getInstance().loadImageSync(picUrl); 外面 ...

  7. (原)Android在子线程用handler发送的消息,主线程是怎么loop到的?

    来自知乎:https://www.zhihu.com/question/48130951?sort=created   大家都知道Android的Looper是ThreadLocal方式实现,每个线程 ...

  8. UNIX环境高级编程——主线程与子线程的退出关系

    我们在一个线程中经常会创建另外的新线程,如果主线程退出,会不会影响它所创建的新线程呢?下面就来讨论一下. 1.  主线程等待新线程先结束退出,主线程后退出.正常执行. 示例代码: #include & ...

  9. Android 快速切换到主线程更新UI的几种方法

    此最近看了网上,在子线程更新UI的方法,说法很多,但都不是很全面.在此我争取做到总结的全面一些,希望以后对自己,对大家都有一些帮助. 方法一: view.post(Runnable action) 假 ...

随机推荐

  1. python初学者必看学习路线图!!!

    python应该是近几年比较火的语言之一,很多人刚学python不知道该如何学习,尤其是没有编程基础想要从事程序员工作的小白,想必应该都会有此疑惑,包括我刚学python的时候也是通过从网上查找相关资 ...

  2. Mac 下 Docker 运行较慢的原因分析及个人见解

    在mac 使用 docker 的时候,我总感觉程序在 docker 下运行速度很慢,接下来我一一分析我遇到的问题,希望大家能进行合理的讨论和建议. 问题: valet 下打开 laravel 首页耗时 ...

  3. vue-element-admin 模板 登录页面 post请求通过django的csrf认证,处理304错误

    经过一天的研究,终于把 vue-admin-template 模板的 post 请求 和django的api 弄通了 没有了那该死的304报错了 直接贴代码: 在main.js中 我直接给设置了一个 ...

  4. 解决挖矿病毒【Xmrig miner 】CPU 100%服务器卡死问题

    背景: 突然有一天,服务器访问很慢很慢,进程查看发现CPU是100%,而且没有任何降低的意思 收集: 打开任务管理器,进程查看中CPU排序,发现一个System的进程,第一想法以为是空闲利用,发现结束 ...

  5. 自动控制理论的MATLAB仿真实例(一)

    拉普拉斯变换及其反变换 Laplace变换及其反变换的定义为:

  6. Object.keys()方法

    一.定义和用法 返回对象的可枚举属性和方法的名称.二.参数 obj:要返回器枚举自身属性的对象.三.返回值 返回一个所有元素为字符串的数组,其元素来自于从给定的obj里可直接枚举的属性.这些属性的顺序 ...

  7. 将config从内部移动到外部 3部曲

    1 创建 public/config.js /* eslint-disable no-shadow-restricted-names */ // eslint-disable-next-line no ...

  8. BNP Paribas Cardif Claims Management

    2月20日: 查看任务介绍,二分类问题,评价标准logloss 下载数据 2月21~27日: 查看数据组成,标识分类变量.离散变量.连续变量. 发现连续数据有大量非随机空缺(占总量一半),主要集中在v ...

  9. Java-字符流练习。(新手)

    参考手册:   关键字: write()   写入存取 close()  结束 flush()   刷新缓冲区(缓冲区就是临时存放数据的区域.) currentTimeMillis()  返回以毫秒为 ...

  10. idea安装 阿里巴巴Java编码准则插件

    首先还是打开熟悉的idea 在marketplace 输入 alibaba 我这是已经安装过了 下载完成之后重启idea生效 如果需要那就手动的扫描 当然已经自动的扫描了 如果你的代码不符合阿里的标准 ...