RunLoop 总结:RunLoop的应用场景(二)

2024-02-05 15:38
文章标签 总结 应用 场景 runloop

本文主要是介绍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的两种写法:

// 第一种写法
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer fire];
// 第二种写法
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面的两种写法其实是等价的。第二种写法,默认也是将timer添加到NSDefaultRunLoopMode下的,并且会自动fire。。
要验证这一结论,我们只需要在timerUpdate方法中,将当前runLoop的currentMode打印出来即可。

- (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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

然后,我们在滑动tableView的时候timerUpdate方法,并不会调用。
* 原因是啥呢?*
原因是当我们滑动scrollView时,主线程的RunLoop 会切换到UITrackingRunLoopMode这个Mode,执行的也是UITrackingRunLoopMode下的任务(Mode中的item),而timer 是添加在NSDefaultRunLoopMode下的,所以timer任务并不会执行,只有当UITrackingRunLoopMode的任务执行完毕,runloop切换到NSDefaultRunLoopMode后,才会继续执行timer。

* 要如何解决这一问题呢?*
解决方法很简单,我们只需要在添加timer 时,将mode 设置为NSRunLoopCommonModes即可。

- (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中,就不要用了
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

从RunLoop官方文档和 iPhonedevwiki中的CFRunLoop可以看出,NSRunLoopCommonModes并不是一种Mode,而是一种特殊的标记,关联的有一个set ,官方文档说:For Cocoa applications, this set includes the default, modal, and event tracking modes by default.(默认包含NSDefaultRunLoopMode、NSModalPanelRunLoopMode、NSEventTrackingRunLoopMode)
添加到NSRunLoopCommonModes中的还没有执行的任务,会在mode切换时,再次添加到当前的mode中,这样就能保证不管当前runloop切换到哪一个mode,任务都能正常执行。并且被添加到NSRunLoopCommonModes中的任务会存储在runloop 的commonModeItems中。

其他一些关于timer的坑

我们在子线程中使用timer,也可以解决上面的问题,但是需要注意的是把timer加入到当前runloop后,必须让runloop 运行起来,否则timer仅执行一次。

示例代码:

//首先是创建一个子线程
- (void)createThread
{NSThread *subThread = [[NSThread alloc] initWithTarget:self selector:@selector(timerTest) object:nil];[subThread start];self.subThread = subThread;
}// 创建timer,并添加到runloop的mode中
- (void)timerTest
{@autoreleasepool {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 scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];[[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;});
}
  • 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
  • 37
  • 38
  • 39
  • 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
  • 37
  • 38
  • 39

添加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设置为NSDefaultRunLoopModeNSRunLoopCommonModes均可,但是需要保证RunLoop在运行,且其中有任务。

文中的示例代码都来自:RunLoopDemos中的RunLoopDemo02


转自:http://blog.csdn.net/u011619283/article/details/53436907

这篇关于RunLoop 总结:RunLoop的应用场景(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/681376

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表

Windows Docker端口占用错误及解决方案总结

《WindowsDocker端口占用错误及解决方案总结》在Windows环境下使用Docker容器时,端口占用错误是开发和运维中常见且棘手的问题,本文将深入剖析该问题的成因,介绍如何通过查看端口分配... 目录引言Windows docker 端口占用错误及解决方案汇总端口冲突形成原因解析诊断当前端口情况解

Python结合PyWebView库打造跨平台桌面应用

《Python结合PyWebView库打造跨平台桌面应用》随着Web技术的发展,将HTML/CSS/JavaScript与Python结合构建桌面应用成为可能,本文将系统讲解如何使用PyWebView... 目录一、技术原理与优势分析1.1 架构原理1.2 核心优势二、开发环境搭建2.1 安装依赖2.2 验

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

SpringShell命令行之交互式Shell应用开发方式

《SpringShell命令行之交互式Shell应用开发方式》本文将深入探讨SpringShell的核心特性、实现方式及应用场景,帮助开发者掌握这一强大工具,具有很好的参考价值,希望对大家有所帮助,如... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定