李洪强iOS开发之RunLoop的原理和核心机制
李洪强iOS开发之RunLoop的原理和核心机制
搞iOS之后一直没有深入研究过RunLoop,非常的惭愧。刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研究了RunLoop的原理和特性。
RunLoop的定义
当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。
RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。
Foundation: NSRunLoop
Core Foundation: CFRunLoop 核心部分,代码开源,C 语言编写,跨平台
目的
通过RunLoop机制实现省电,流畅,响应速度快,用户体验好
理解
进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源。
RunLoop管理流水线,流水线才不会因为无所事事被工厂销毁;而不需要流水线时,就会辞退RunLoop这个主管,即退出线程,把所有资源释放。
RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop的循环机制实现,Android的Looper就是类似的机制。
特性
- 主线程的RunLoop在应用启动的时候就会自动创建
- 其他线程则需要在该线程下自己启动
- 不能自己创建RunLoop
- RunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop
- RunLoop负责管理autorelease pools
- RunLoop负责处理消息事件,即输入源事件和计时器事件
RunLoop机制
主线程 (有 RunLoop 的线程) 几乎所有函数都从以下六个之一的函数调起:
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function
用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimationCFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block
消息通知、非延迟的perform、dispatch调用、block回调、KVOCFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CFRunloop is servicing the main desipatch queueCFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function
延迟的perform, 延迟dispatch调用CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function
处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function
由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePortRunLoop 架构

- RunLoop 运行时

主要有以下六种状态:
- kCFRunLoopEntry -- 进入runloop循环
- kCFRunLoopBeforeTimers -- 处理定时调用前回调
- kCFRunLoopBeforeSources -- 处理input sources的事件
- kCFRunLoopBeforeWaiting -- runloop睡眠前调用
- kCFRunLoopAfterWaiting -- runloop唤醒后调用
- kCFRunLoopExit -- 退出runloop
RunLoop 运行时调用栈
- 主线程App运行时

- RunLoopObserver与Autorelease Pool的关系

UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。
- RunLoop的挂起与唤醒

指定用于唤醒的 mach_port 端口
调用 mach_msg 监听唤醒端口,被唤醒前系统内核将这个线程挂起,停留在mach_msg_trap状态。
由另一个线程向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续工作。
RunLoop支持的消息事件(Events)
- RunLoop

支持接收处理输入源(Input Source)事件,包括:
系统的Mach Port事件,是一种通讯事件
自定义输入事件支持接受处理定时源(Timer)事件
在启动RunLoop之前,必须添加监听的输入源事件或者定时源事件,否则调用[runloop run]会直接返回,而不会进入循环让线程长驻。
如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。
没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。
//错误做法
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (!self.isCancelled && !self.isFinished) {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
};
//正确做法
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
@autoreleasepool {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
}
Run Loop Modes
理解
Run Loop Mode就是流水线上支持生产的产品类型,流水线在一个时刻只能在一种模式下运行,生产某一类型的产品。消息事件就是订单。Cocoa定义了四中Mode
Default:NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
Connection:NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件
Event tracking:UITrackingRunLoopMode,拖动事件
Common mode:NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式RunLoop可以通过[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]来指定在一段时间内的运行模式。如果不指定的话,RunLoop默认会运行在Default下(不断重复调用runMode:NSDefaultRunLoopMode beforDate:)
在主线程启动一个计时器Timer,然后拖动UITableView或者UIScrollView,计时器不执行。这是因为,为了更好的用户体验,在主线程中Event tracking模式的优先级最高。在用户拖动控件时,主线程的Run Loop是运行在Event tracking Mode下,而创建的Timer是默认关联为Default Mode,因此系统不会立即执行Default Mode下接收的事件。解决方法:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerFireMethod:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//或
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[timer fire];
Run Loop应用实践
Run Loop主要有以下三个应用场景:
- 维护线程的生命周期,让线程不自动退出,isFinished为Yes时退出。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
@autoreleasepool {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
}
- 创建常驻线程,执行一些会一直存在的任务。该线程的生命周期跟App相同
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
- 在一定时间内监听某种事件,或执行某种任务的线程
如下代码,在30分钟内,每隔30s执行onTimerFired:。这种场景一般会出现在,如我需要在应用启动之后,在一定时间内持续更新某项数据。
@autoreleasepool {
NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
target:self
selector:@selector(onTimerFired:)
userInfo:nil
repeats:YES];
[runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
- AFNetworking中RunLoop的创建
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 这里主要是监听某个 port,目的是让这个 Thread 不会回收
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
李洪强iOS开发之RunLoop的原理和核心机制的更多相关文章
- 李洪强iOS开发之Block和协议
李洪强iOS开发之Block和协议 OC语言BLOCK和协议 一.BOLCK (一)简介 BLOCK是什么?苹果推荐的类型,效率高,在运行中保存代码.用来封装和保存代码,有点像函数,BLOCK可以在任 ...
- 李洪强iOS开发之iOS社区收集
李洪强iOS开发之iOS社区收集 项目 简述 github 全球最大的代码仓库,无论是iOS开发还是Android开发没有人不知道这个网站,它也是一个社区,你可以去follow(关注)某些人或公司. ...
- 李洪强iOS开发之iOS工具收集
李洪强iOS开发之iOS工具收集 项目 简述 日期 我是怎么慢慢变懒的 : Jenkins + 蒲公英 使用Jenkins + 蒲公英使得项目打包给测试人员自动化,大大节省了劳动力 2015.04.1 ...
- 李洪强iOS开发之iOS学习方法收集
李洪强iOS开发之iOS学习方法收集 在这里收集一些iOS学习方法,会不断更新 项目 简述 日期 一年多iOS开发总结 作者总结了自己一年多的iOS学习经验,对于iOS初学者来说很多地方是可以借鉴的 ...
- 李洪强iOS开发之iOS好文章收集
李洪强iOS开发之iOS好文章收集 该文收集朋友们转发或自己的写的技术文章,如果你也有相关的好文章,欢迎留言,当好文章多的时候,我会对这些好文章进行分门别类 文章 简述 日期 直播服务配置 使用 ng ...
- 李洪强IOS开发之iOS好项目收集
李洪强IOS开发之iOS好项目收集 在这里收集一些最近出现的比较实用好玩的框架或者项目,会不断更新 项目 简述 日期 SCTableViewCell 类似与QQ侧滑删除Cell的Demo 201501 ...
- 李洪强iOS开发之iOS技术博客
李洪强iOS开发之iOS技术博客 注意:访问博客请直接点击博客,不要点击后面的RSS地址 博客地址 RSS地址 南峰子的技术博客 剑尖博客 图拉鼎 Henry Lee Dev Talk ...
- 李洪强iOS开发之带placeHolder的Textview
李洪强iOS开发之带placeHolder的Textview 01 - 创建工过程,定义全局属性,遵守textview的代理协议 02 - 添加一个textview和一个label 03 - 实现 ...
- 李洪强iOS开发之OC[014] -对象的存储细节
// // main.m // 13 - 对象的存储细节 // // Created by vic fan on 16/7/9. // Copyright © 2016年 李洪强. All r ...
随机推荐
- 前端打包--source-map=false作用
参考:http://www.cnblogs.com/axl234/p/6500534.htmlng serve默认会产生.map文件,该文件保存有原始代码与运行代码的映射关系, 浏览器可以通过它找到原 ...
- 导出csv文件(php实现)
<?php namespace App\Library\lib; class CsvLib { /** * [构造函数] * */ public function __construct() { ...
- 条款28:避免返回handles指向对象内部的成分(Avoid returning "handles" to objects internals)
NOTE: 1.避免返回handles(包括references 指针 迭代器)指向对象内部.遵守这个条款可增加分装性,帮助const 成员函数的行为像个const,并将发生“虚吊号码牌”(dangl ...
- 【Codeforces 449A】Jzzhu and Chocolate
[链接] 我是链接,点我呀:) [题意] 题意 [题解] 设最后行分成了x行,列分成了y列. 那么答案就是floor(n/x)floor(n/y) 然后x+y-2=k //即平均分配x行.y列 我们可 ...
- ZOJ 3824 Fiber-optic Network
Fiber-optic Network Time Limit: 15000ms Memory Limit: 262144KB This problem will be judged on ZJU. O ...
- MyBatis使用Mapper动态代理开发Dao层
开发规范 Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同原始Dao接口实现类方法. Mappe ...
- Monkey King(左偏树)
洛谷传送门 每次给出要争吵的猴子a和b,用并查集判断如果他们是朋友输出-1 如果不是,找出a,b在的堆的根A,B,分别合并A,B的左右孩子,再合并一下. 之后把A,B的数据更改一下:权值除以2,左右孩 ...
- bzoj 1702 贪心,前缀和
[Usaco2007 Mar]Gold Balanced Lineup 平衡的队列 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 807 Solved: ...
- springmvc和dubbo整合时,不配置spring listener报错找不到/WEB-INF/config/applicationContext.xml
原因,dubbo2.6.3版本开始就需要先在listener中配置容器,否则报错,2.6.2版本则不需要
- secure上传图片
1.rz -bye 2.upload as ascii 去掉勾选框