RunLoop 总结:RunLoop的应用场景(二)
上一篇讲了使用RunLoop保证子线程的长时间存活,而不是执行完任务后就立刻销毁的应用场景。这一篇就讲述一下RunLoop如何保证NSTimer在视图滑动时,依然能正常运转。
参考资料
好的书籍都是值得反复看的,那好的文章,好的资料也值得我们反复看。我们在不同的阶段来相同的文章或资料或书籍都能有不同的收获,那它就是好文章,好书籍,好资料。
关于iOS 中的RunLoop资料非常的少,以下资料都是非常好的。
CF框架源码(这是一份很重要的源码,可以看到CF框架的每一次迭代,我们可以下载最新的版本来分析,或与以下文章对比学习。目前最新的是CF-1153.18.tar.gz)
RunLoop官方文档(学习iOS的任何技术,官方文档都是入门或深入的极好手册;我们也可以在Xcode--->Help--->Docementation and API Reference --->搜索RunLoop---> Guides(59)--->《Threading Programming Guide:Run Loops》这篇即是)
深入理解RunLoop(不要看到右边滚动条很长,其实文章占篇幅2/5左右,下面有很多的评论,可见这篇文章的火热)
RunLoop个人小结 (这是一篇总结的很通俗容易理解的文章)
sunnyxx线下分享RunLoop(这是一份关于线下分享与讨论RunLoop的视频,备用地址:https://pan.baidu.com/s/1pLm4Vf9)
iPhonedevwiki中的CFRunLoop(commonModes中其实包含了三种Mode,我们通常知道两种,还有一种是啥,你知道么?)
维基百科中的Event loop(可以看看这篇文章了解一下事件循环)
使用场景
1.我们经常会在应用中看到tableView 的header 上是一个横向ScrollView,一般我们使用NSTimer,每隔几秒切换一张图片。可是当我们滑动tableView的时候,顶部的scollView并不会切换图片,这可怎么办呢?
2.界面上除了有tableView,还有显示倒计时的Label,当我们在滑动tableView时,倒计时就停止了,这又该怎么办呢?
场景中的代码实现
我们的定时器Timer是怎么写的呢?
一般的做法是,在主线程(可能是某控制器的viewDidLoad方法)中,创建Timer。
可能会有两种写法,但是都有上面的问题,下面先看下Timer的两种写法:
|
1
2
3
4
5
|
// 第一种写法NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];[timer fire];// 第二种写法NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];[timer fire]; |
上面的两种写法其实是等价的。第二种写法,默认也是将timer添加到NSDefaultRunLoopMode下的。
要验证这一结论,我们只需要在timerUpdate方法中,将当前runLoop的currentMode打印出来即可。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
- (void)timerUpdate{ NSLog(@"当前线程:%@",[NSThread currentThread]); NSLog(@"启动RunLoop后--%@",[NSRunLoop currentRunLoop].currentMode);// NSLog(@"currentRunLoop:%@",[NSRunLoop currentRunLoop]); dispatch_async(dispatch_get_main_queue(), ^{ self.count ++; NSString *timerText = [NSString stringWithFormat:@"计时器:%ld",self.count]; self.timerLabel.text = timerText; });}// 控制台输出结果:2016-12-02 15:33:57.829 RunLoopDemo02[6698:541533] 当前线程:<nsthread: 0x600000065500>{number = 1, name = main}2016-12-02 15:33:57.829 RunLoopDemo02[6698:541533] 启动RunLoop后--kCFRunLoopDefaultMode</nsthread: 0x600000065500> |
然后,我们在滑动tableView的时候timerUpdate方法,并不会调用。
原因是啥呢?
原因是当我们滑动scrollView时,主线程的RunLoop 会切换到UITrackingRunLoopMode这个Mode,执行的也是UITrackingRunLoopMode下的任务(Mode中的item),而timer 是添加在NSDefaultRunLoopMode下的,所以timer任务并不会执行,只有当UITrackingRunLoopMode的任务执行完毕,runloop切换到NSDefaultRunLoopMode后,才会继续执行timer。
要如何解决这一问题呢?
解决方法很简单,我们只需要在添加timer 时,将mode 设置为NSRunLoopCommonModes即可。
|
1
2
3
4
5
6
7
8
|
- (void)timerTest{ // 第一种写法 NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; [timer fire]; // 第二种写法,因为是固定添加到defaultMode中,就不要用了} |
从RunLoop官方文档和 iPhonedevwiki中的CFRunLoop可以看出,NSRunLoopCommonModes并不是一种Mode,而是一种特殊的标记,包含三种mode(kCFRunLoopDefaultMode、NSTaskDeathCheckMode、UITrackingRunLoopMode),添加到NSRunLoopCommonModes中的还没有执行的任务,会在mode切换时,再次添加到当前的mode中,这样就能保证不管当前runloop切换到哪一个mode,任务都能正常执行。并且被添加到NSRunLoopCommonModes中的任务会存储在runloop 的commonModeItems中。
其他一些关于timer的坑
我们在子线程中使用timer,也可以解决上面的问题,但是需要注意的是把timer加入到当前runloop后,必须让runloop 运行起来,否则timer仅执行一次。
示例代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
//首先是创建一个子线程- (void)createThread{ NSThread *subThread = [[NSThread alloc] initWithTarget:self selector:@selector(timerTest) object:nil]; [subThread start]; self.subThread = subThread;}// 创建timer,并添加到runloop的mode中- (void)timerTest{ NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; NSLog(@"启动RunLoop前--%@",runLoop.currentMode); NSLog(@"currentRunLoop:%@",[NSRunLoop currentRunLoop]); // 第一种写法,改正前// NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];// [timer fire]; // 第二种写法 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES]; [timer fire]; [[NSRunLoop currentRunLoop] run];}//更新label- (void)timerUpdate{ NSLog(@"当前线程:%@",[NSThread currentThread]); NSLog(@"启动RunLoop后--%@",[NSRunLoop currentRunLoop].currentMode); NSLog(@"currentRunLoop:%@",[NSRunLoop currentRunLoop]); dispatch_async(dispatch_get_main_queue(), ^{ self.count ++; NSString *timerText = [NSString stringWithFormat:@"计时器:%ld",self.count]; self.timerLabel.text = timerText; });} |
添加timer 前的控制台输出:
添加timer前的runloop
添加timer后的控制台输出:
添加timer后的runloop
从控制台输出可以看出,timer确实被添加到NSDefaultRunLoopMode中了。可是添加到子线程中的NSDefaultRunLoopMode里,无论如何滚动,timer都能够很正常的运转。这又是为啥呢?
这就是多线程与runloop的关系了,每一个线程都有一个与之关联的RunLoop,而每一个RunLoop可能会有多个Mode。CPU会在多个线程间切换来执行任务,呈现出多个线程同时执行的效果。执行的任务其实就是RunLoop去各个Mode里执行各个item。因为RunLoop是独立的两个,相互不会影响,所以在子线程添加timer,滑动视图时,timer能正常运行。
总结
1、如果是在主线程中运行timer,想要timer在某界面有视图滚动时,依然能正常运转,那么将timer添加到RunLoop中时,就需要设置mode 为NSRunLoopCommonModes。
2、如果是在子线程中运行timer,那么将timer添加到RunLoop中后,Mode设置为NSDefaultRunLoopMode或NSRunLoopCommonModes均可,但是需要保证RunLoop在运行,且其中有任务。
文中的示例代码都来自:RunLoopDemos中的RunLoopDemo02
RunLoop 总结:RunLoop的应用场景(二)的更多相关文章
- 何为RunLoop?RunLoop有哪些应用场景?
一.RunLoop的作用 一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生 ...
- 使用WPF实现3D场景[二]
原文:使用WPF实现3D场景[二] 在上一篇的文章里我们知道如何构造一个简单的三维场景,这次的课程我将和大家一起来研究如何用代码,完成对建立好了的三维场景的观察. 首先看一下DEMO的界面: ...
- 搭建LoadRunner中的场景(二) 集合点
Rendezvous: 这个单词来自于法语,军队集合的意思.LoadRunner中是指各虚拟用户在同一时刻完成指定的操作. 一. 集合点设置步骤 1. 在脚本中需要测试并发性能的操作之前加入集合点. ...
- redis的数据类型与应用场景(二)
1. 如何学习 redis有好多数据类型,有这么多数据类型,我们不可能每个都记得完完全全.但是我们必须知道它有哪些数据类型,每个数据类型是怎样的,有什么作用.redis的每一个数据类型都有一大堆命令, ...
- ionic 2 起航 控件的使用 客户列表场景(二)
首先放出我hithub项目代码例子,有兴趣研究探讨的同学可以去看看 https://github.com/linyuebin2016/ionic2.git 下面我们来尝试下第一个项目场景 一份客户的列 ...
- RunLoop 总结:RunLoop的应用场景(一)
参考资料 好的书籍都是值得反复看的,那好的文章,好的资料也值得我们反复看.我们在不同的阶段来相同的文章或资料或书籍都能有不同的收获,那它就是好文章,好书籍,好资料.关于iOS 中的RunLoop资料非 ...
- Runloop的再学习之浅析(一)
一,认识RunLoop 我的理解: 1. 在编程的世界里,万物皆对象.所以RunLoop 实际上也是一个对象,这个对象管理了其需要 处理的事件和消息,并提供了一个入口函数来执行上面 Event Loo ...
- iOS开发——高级篇——Runloop相关一
一.什么是runLoop 1.说白了,runloop就是运行循环 2.runloop,他是多线程的法宝 通常来讲,一个线程一次只能执行一个任务,执行完之后就退出线程.但是,对于主线程是不能退出的,因此 ...
- runloop 小记
一.什么是runLoop 1.说白了,runloop就是运行循环 2.runloop,他是多线程的法宝 通常来讲,一个线程一次只能执行一个任务,执行完之后就退出线程.但是,对于主线程是不能退出的,因此 ...
随机推荐
- 我的MYSQL学习心得(十) 自定义存储过程和函数
我的MYSQL学习心得(十) 自定义存储过程和函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心 ...
- .NET开源进行时:消除误解、努力前行(本文首发于《程序员》2015第10A期的原始版本)
2014年11月12日,ASP.NET之父.微软云计算与企业级产品工程部执行副总裁Scott Guthrie,在Connect全球开发者在线会议上宣布,微软将开源全部.NET核心运行时,并将.NET ...
- 2000条你应知的WPF小姿势 基础篇<34-39 Unhandled Exceptions和Resource>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000Things You Should Know About C# 和 2,0 ...
- TODO:数据库优化之分页
TODO:数据库优化之分页 本文的例子是以MongoDB数据库为准,其它数据库各位也可以举一反三进行优化. 在MongoDB中分页使用 a.skip(n)跳过前n个匹配的文档: b.limit(m)返 ...
- Entity Framework 6 Recipes 2nd Edition(10-9)译 -> 在多对多关系中为插入和删除使用存储过程
10-9. 在多对多关系中为插入和删除使用存储过程 问题 想要在一个无载荷的多对多关系中使用存储过程(存储过程只影响关系的连接表) 解决方案 假设有一个多对多关系的作者( Author)表和书籍( B ...
- MVC5 网站开发之六 管理员 1、登录、验证和注销
上次业务逻辑和展示层的架构都写了,可以开始进行具体功能的实现,这次先实现管理员的登录.验证和注销功能. 目录 奔跑吧,代码小哥! MVC5网站开发之一 总体概述 MVC5 网站开发之二 创建项目 MV ...
- Bootstrap相关资料
WEB项目中,使用Bootstrap较多.但是一些插件却比较确实,所以整理了一份Bootstrap相关插件的地址.基本满足日常WEB开发中插件需求.并且还挺好用的 bootstrap说明文档,有问题可 ...
- 在Windows Server 2012中如何快速开关桌面上经典的“计算机、我的文档”等通用图标
我们都知道,在Windows Server 2012系列的服务器版本中都已经引入了Modern的现代界面作为默认的用户交互界面,同时满足视觉一致化,新版的服务器管理程序也做成了扁平化.因此传统的计算机 ...
- 使用Design包实现QQ动画侧滑效果和滑动菜单导航
Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的Android Design Support Library,在这个supp ...
- Autofac 的属性注入,IOC的坑
Autofac 是一款优秀的IOC的开源工具,完美的适配.Net特性,但是有时候我们想通过属性注入的方式来获取我们注入的对象,对不起,有时候你还真是获取不到,这因为什么呢? 1.你对Autofac 不 ...