一直想写一篇关于runloop学习有所得的文章,总是没有很好的例子。游戏中有一个计时功能在主线程中调用:

1 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

的方法。但是当每0.01秒进行一次repeat操作时,NSTimer是不准的,严重滞后,而改成0.1秒repeat操作,则这种滞后要好一些。

导致误差的原因是我在使用“scheduledTimerWithTimeInterval”方法时,NSTimer实例是被加到当前runloop中的,模式是NSDefaultRunLoopMode。而“当前runloop”就是应用程序的main runloop,此main runloop负责了所有的主线程事件,这其中包括了UI界面的各种事件。当主线程中进行复杂的运算,或者进行UI界面操作时,由于在main runloop中NSTimer是同步交付的被“阻塞”,而模式也有可能会改变。因此,就会导致NSTimer计时出现延误。

解决这种误差的方法,一种是在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果;另一种是仍然在主线程中进行NSTimer操作,但是将NSTimer实例加到main runloop的特定mode(模式)中。避免被复杂运算操作或者UI界面刷新所干扰。

方法一:

在开始计时的地方:

1 if (self.timer) {
2 [self.timer invalidate];
3 self.timer = nil;
4 }
5 self.timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
6 [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

[NSRunLoop currentRunLoop]获取的就是“main runloop”,使用NSRunLoopCommonModes模式,将NSTimer加入其中。

(借鉴了博文:)

方法二:

开辟子线程:(使用子线程的runloop)

1 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
2 [thread start];
1 - (void)newThread
2 {
3 @autoreleasepool
4 {
5 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
6 [[NSRunLoop currentRunLoop] run];
7 }
8 }

在子线程中将NSTimer以默认方式加到该线程的runloop中,启动子线程。

方法三:

使用GCD,同样也是多线程方式:

声明全局成员变量

1 dispatch_source_t _timers;
 1     uint64_t interval = 0.01 * NSEC_PER_SEC;
2 dispatch_queue_t queue = dispatch_queue_create("my queue", 0);
3 _timers = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
4 dispatch_source_set_timer(_timers, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
5 __weak ViewController *blockSelf = self;
6 dispatch_source_set_event_handler(_timers, ^()
7 {
8 NSLog(@"Timer %@", [NSThread currentThread]);
9 [blockSelf addTime];
10 });
11 dispatch_resume(_timers);

然后在主线程中修改UI界面:

1 dispatch_async(dispatch_get_main_queue(), ^{
2 self.label.text = [NSString stringWithFormat:@"%.2f", self.timeCount/100];
3 });

游戏源代码可见:

总结:

runloop是一个看似很神秘的东西,其实一点也不神秘。每个线程都有一个实际已经存在的runloop。比如我们的主线程,在主函数的UIApplication中:

1 UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))

系统就为我们将主线程的main runloop隐式的启动了。runloop顾名思义就是一个“循环”,他不停地运行,从程序开始到程序退出。正是由于这个“循环”在不断地监听各种事件,程序才有能力检测到用户的各种触摸交互、网络返回的数据才会被检测到、定时器才会在预定的时间触发操作……

runloop只接受两种任务:输入源和定时源。本文中说的就是定时源。默认状态下,子线程的runloop中没有加入我们自己的源,那么我们在子线程中使用自己的定时器时,就需要自己加到runloop中,并启动该子线程的runloop,这样才能正确的运行定时器。

在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法的更多相关文章

  1. Android开发UI之在子线程中更新UI

    转自第一行代码-Android Android是不允许在子线程中进行UI操作的.在子线程中去执行耗时操作,然后根据任务的执行结果来更新相应的UI控件,需要用到Android提供的异步消息处理机制. 代 ...

  2. Android 在子线程中更新UI

    今天在做练习时,在一个新开启的线程中调用“Toast.makeText(MainActivity.this, "登陆成功",Toast.LENGTH_SHORT).show();” ...

  3. EXC_BAD_ACCESS(code=2,address=0xcc 异常解决 及 建议不要在子线程中刷新界面

    iOS 上不建议在非主线程进行UI操作,在非主线程进行UI操作有很大几率会导致程序崩溃,或者出现预期之外的效果. 我开始不知道这一点,在子线程中进行了弹窗操作,结果程序就出问题了! 报的错误是(EXC ...

  4. android UI 操作 不要在子线程中操作UI

    不管是android ,还是 ios ,请不要在子线程中操作UI,有时有些崩溃,从报错上看不出什么原因,就有可能是子线程操作了UI:切记,切记! 请放在主线程例: activity.runOnUiTh ...

  5. 【转载】Delphi7从子线程中发送消息到主线程触发事件执行

    在对数据库的操作时,有时要用一个子线程来进行后台的数据操作.比如说数据备份,转档什么的.在主窗口还能同是进行其它操作.而有时后台每处理一个数据文件,要向主窗口发送消息,让主窗口实时显示处理进度在窗口上 ...

  6. iOS开发之线程间的MachPort通信与子线程中的Notification转发

    如题,今天的博客我们就来记录一下iOS开发中使用MachPort来实现线程间的通信,然后使用该知识点来转发子线程中所发出的Notification.简单的说,MachPort的工作方式其实是将NSMa ...

  7. Android多线程之(一)View.post()源码分析——在子线程中更新UI

    提起View.post(),相信不少童鞋一点都不陌生,它用得最多的有两个功能,使用简便而且实用: 1)在子线程中更新UI.从子线程中切换到主线程更新UI,不需要额外new一个Handler实例来实现. ...

  8. 在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()

    在子线程中new一个Handler为什么会报以下错误? java.lang.RuntimeException:  Can't create handler inside thread that has ...

  9. 如何在子线程中使用Toast和更新UI

    因为没一个Looper处理消息循环,所以子线程中无法使用Toast 方法: Looper.prepare(); Toast.makeText(getActivity(),"刷到底啦" ...

随机推荐

  1. Python基础 (yield生成器)

    如果在一个函数中使用了yield,那么这个函数实际上生成的是一个生成器函数 ,返回的是一个generator object.生成器是实现迭代的一种方式 特点: 其实返回的就是可以的迭代对象 和迭代的方 ...

  2. Python模块(pickle)

    pickle 序列化和反序列化 序列化作用 序列化使用 cPickle使用例 Python提供一个标准的模块,称为pickle.使用它你可以在一个文件中储存任何Python对象,之后你又可以把它完整无 ...

  3. C# web api 返回类型设置为json的两种方法

    每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不 ...

  4. 收集一些java相关的文章

    有些文章看了,以后想再看已经忘了在哪里了.故在此一一记录下那些值得看的文章. 1:如何确定Java对象的大小 中文版本 :http://article.yeeyan.org/view/104091/6 ...

  5. PL/SQL Developer连接到Oracle 12c

    安装32位的Oracle客户端instantclient-basic-nt-12.1.0.1.0.zip 下载地址:http://www.oracle.com/technetwork/topics/w ...

  6. SQLSERVER:通过sys.tables实现批量删表、快速统计多表记录和

    SQLSERVER:通过sys.tables实现批量删表,或者回滚表 begin try drop table #temp10 end try begin catch end catch select ...

  7. centos 6.4下安装postgresql 9.2

    我的linux版本是centos 6.4 ,准备安装postgresql 9.2 根据官方说明: http://www.postgresql.org/download/linux/redhat/ 缺省 ...

  8. PostgreSQL Replication之第十章 配置Slony(3)

    10.3 复制您的第一个数据库 这个小小的介绍之后,我们可以继续前进并复制我们的第一个数据库.要做到这一点,我们可以在一个数据库实例上创建两个数据库.我们想简单地在这两个数据库之间进行复制. [ 您在 ...

  9. Oracle-数据库

    Oracle 1.特点 关系型数据库 采用二维表的行使管理数据库 具有行和列  表间存在关联关系 2.安装 数据库(11g) 版本类型 32位    64位  安装类型 桌面类 本机开发 服务器类 生 ...

  10. int 与 Integer--话说数组转集合

    话说是自从JDK5后,而这就可以自动进行类型转换了. 当然,区别还是有的,就是对象和“非对象”什么的.可是,今天进行一个测试,出了一个小问题,现将代码贴下: 代码0:先来一个正常点的,用String进 ...