iOS开发 - RunLoop理解
RunLoop概念
运行循环,一个 run loop 就是一个事件处理的循环,用来不停的调度工作以及处理事件
作用
- 保持程序的持续运行
- 监听处理App中的各种事件(触摸事件,定时器事件,selector事件)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- 一次RunLoop循环负责绘制屏幕上所有的点
入口函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain()此函数内部就启动了一个RunLoop,所以此函数一直没有返回,保持了程序的持续运行,这个默认启动的RunLoop是跟主线程相关的
RunLoop对象
- Foundation框架下 : NSRunLoop (基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的)
- Core Foundation框架下 : CFRunLoopRef (纯 C 函数的 API,所有这些 API 都是线程安全的)
RunLoop与线程
- 每条线程有唯一的一个与之对应的RunLoop对象
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要手动创建
- RunLoop在第一次获取时创建,在线程结束时销毁
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];//主线程对应的RunLoop
[NSRunLoop mainRunLoop];//当前线程对应的RunLoop
currentRunloop.getCFRunLoop;//转化为CFRunLoop CFRunLoopGetMain();
CFRunLoopGetCurrent(); //开启一个子线程
[[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start]; -(void)run { //创建子线程对应的RunLoop,currentRunLoop 懒加载的
[NSRunLoop currentRunLoop]; }
以下是苹果官方源码,通过分析源代码可以看出利用pthread作为全局字典中的key,并创建与之对应的RunLoop作为Value,RunLoop在我们获取的时候创建,不获取不创建,主线程的RunLoop在一开始就自动创建。线程与RunLoop是一一对应的关系
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
} CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
} //全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问 Dictionary 时的锁
static CFLock_t loopsLock = CFLockInit;
//获取一个 pthread 对应的 RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//如果传进来的线程等于 0
if (pthread_equal(t, kNilPthreadT)) {
//当前线程等于主线程
t = pthread_main_thread_np();
}
//给操作加锁
__CFLock(&loopsLock);
//如果当前RunLoop为空,创建。
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, , NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 从字典中获取当前线程的RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果当前线程的runloop不存在,那么就为该线程创建一个对应的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 把当前子线程和对应的runloop保存到字典中
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if ( == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
RunLoop相关类
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef (基于时间的触发器)
- CFRunLoopObserverRef
说明:一个RunLoop包含若干个Mode,每个Mode又包含若干个Source、Timer、Observer,每次RunLoop启动时,只能指定一个Mode,这个Mode被称作CurrentMode,如果需要切换Mode,只能退出Loop,在重新指定一个Mode进入,这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
1.CFRunLoopSourceRef :事件源(输入源)
- Source0:非基于Port的 (用户主动触发的事件)
- Source1:基于Port的 (系统内部的消息事件)
(Port是线程间通信的一种方式,如果两个线程之间想通信,可以通过Port来通信。)
2.CFRunLoopTimerRef
基于时间触发器,当其加入RunLoop时,RunLoop会注册对应的时间点,当时间点到,RunLoop会被唤醒执行里面的回调。
3.CFRunLoopObserverRef
观察者,能够监听RunLoop的状态改变。
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << ),//即将进入Loop kCFRunLoopBeforeTimers = (1UL << ),//即将处理Timer kCFRunLoopBeforeSources = (1UL << ),//即将处理Source kCFRunLoopBeforeWaiting = (1UL << ),//即将进入休眠 kCFRunLoopAfterWaiting = (1UL << ),//刚从休眠中唤醒 kCFRunLoopExit = (1UL << ),//即将退出Loop kCFRunLoopAllActivities = 0x0FFFFFFFU };
-(void)addObserver{ /*
参数一:怎么分配存储空间
参数二:要监听的状态 kCFRunLoopAllActivities 所有的状态
参数三:是否要持续监听
参数四:优先级,总是传0
参数五:当状态改变时候的回调
*/ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, , ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理Sources事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break; default:
break;
} });
//给runloop添加监听者
/*
参数一:要监听哪个runloop
参数二:观察者
参数三:运行模式
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}
4.CFRunLoopModeRef :RunLoop的运行模式
在RunLoop中有多个运行模式,但是RunLoop只能选择一种模式运行,Mode里面至少要有Timer或者Source
系统默认注册了5个Mode:
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
- UIInitializationRunLoopMode:在刚进入App时进入的第一个Mode,启动完成后就不在使用
- GCEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
- NSRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
RunLoop相关问题及解释
1.Timer与滑动控件的问题
问题:当在不停地拖动滑动控件的时候,定时器不工作了
错误回答:runloop的优先级
分析:runloop的几种常用模式:DefaultMode默认模式,以及UITrackingMode模式,CommonModes占位模式,runloop进入一种模式的时候,另一种模式的事件不会去处理,当Timer在运行的时候处于DefaultMode模式,当拖动滑动控件的时候,runloop会立即处理UI事件,切换到UITrackingRunLoopMode模式后,此时DefaultMode模式下的Timer就不工作了
解决:将Timer置于NSRunLoopCommonModes占位模式下
- (void)viewDidLoad { [super viewDidLoad]; //1.创建定时器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //2.将Timer添加到RunLoop中 //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; } - (void)run { NSLog(@"-------- %@",[NSThread currentThread]); }
问题:在多线程开发中,耗时操作我们一般会放在子线程中执行,请问这种线程有什么特点?
实例:假如在上面的定时器的run方法中,执行一个耗时操作,此时会卡住主线程,拖动滑动控件会很不流畅,应该如何解决?
分析:子线程中默认不会开启RunLoop循环,所以子线程在执行完任务之后就会被回收
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(, ), ^{ //1.创建定时器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //2.将Timer添加到RunLoop中 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; //3.让RunLoop运行起来 [[NSRunLoop currentRunLoop] run];//死循环,后面的代码不会执行 NSLog(@"+++++++++"); }); } - (void)run { //耗时操作 [NSThread sleepForTimeInterval:1.0]; NSLog(@"-------- %@",[NSThread currentThread]); }
或者:
- (void)viewDidLoad { [super viewDidLoad]; [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil]; } -(void)time2{ //创建当前线程的RunLoop NSRunLoop *currentLoop = [NSRunLoop currentRunLoop]; //该方法内部自动添加到RunLoop中,并且运行模式是默认模式 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //开启RunLoop [currentLoop run]; } - (void)run { //耗时操作 [NSThread sleepForTimeInterval:1.0]; NSLog(@"run ----- %@ ---- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode); }
此博文会继续不断完善及更新关于涉及到runloop的知识,如果有理解不正确或者有涉及到runloop的应用实例,请留言吧,欢迎讨论
参考文章:http://blog.ibireme.com/2015/05/18/runloop/
iOS开发 - RunLoop理解的更多相关文章
- iOS开发SDWebImageOptions理解
iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUIn ...
- 0112.1——iOS开发之理解iOS中的MVC设计模式
模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程 ...
- iOS开发之理解iOS中的MVC设计模式
模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程 ...
- iOS开发RunLoop学习:一:RunLoop简单介绍
一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementatio ...
- iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题
一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...
- iOS开发RunLoop
最近处于离职状态,时间也多了起来,但是学习还是不能放松,今天总结一下RunLoop,RunLoop属于iOS系统层的东西,还是比较重要的. 一.什么是RunLoop 字面意思看是跑圈,也可以看作运行循 ...
- iOS开发-Runloop详解(简书)
不知道大家有没有想过这个问题,一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件 ...
- 玩转iOS开发 - Runloop 具体解释
Runloop 具体解释
- iOS开发RunLoop学习:三:Runloop相关类(source和Observer)
一:RunLoop相关类: 其中:source0指的是非基于端口por,说白了也就是处理触摸事件,selector事件,source1指的是基于端口的port:是处理系统的一些事件 注意:创建一个Ru ...
随机推荐
- Mac 下Java开发环境安装
一.安装Eclipse 1.官网下载安装文件 http://www.eclipse.org/downloads 2.eclipse安装svn插件 这里须要注意安装的svn的版本号.要和后面的安装的Ja ...
- SE18 BADI定义 / SE19 BADI 实现
明天花30分 再研究下这个: 如果你知道一个BADI名称,可以: 1)使用SE18,输入该BADI名称后,选择Interface,然后查看对应的接口实施样例代码(Example implementat ...
- 基本动画、复合动画设置 平移、缩放、旋转、透明度 编码实现 xml实现
public class VAActivity extends Activity { private ImageView iv_animation; private TextView tv_anima ...
- Understand JavaScript Callback Functions and Use Them
In JavaScript, functions are first-class objects; that is, functions are of the type Object and they ...
- poj-3666
http://vjudge.net/problem/POJ-3666 题目是dp 题目; 简单dp 离散一下就好. 我们先来讲一讲不离散的,简单的懂了,其他的也很容易. dp[i] 代表这个数列以 ...
- hdu-5728 PowMod(数论)
题目链接: PowMod Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others) ...
- Python: PS滤镜--径向模糊
本文用 Python 实现 PS 滤镜中的径向模糊特效,具体的算法原理和效果可以参考之前的博客: http://blog.csdn.net/matrix_space/article/details/3 ...
- python 基础之第五天
###########window路径写法########## In [1]: winpath = 'C:\tmp' In [2]: print winpath C: mp In [3]: winpa ...
- OTL之Oracle开发总结《转》
OTL之Oracle开发总结---转 关 于OTL,网上介绍的也不少,但看来看去也只是官方的那些文档.OTL很好用,结合官方提供的一些例子,多多尝试才能领悟.经过一个月左右的项目开发,对 OTL也 ...
- 【旧文章搬运】关于NtUserBuildHwndList的一点记录~
原文发表于百度空间,2011-04-07========================================================================== 该函数与r ...