RunLoop概念

运行循环,一个 run loop 就是一个事件处理的循环,用来不停的调度工作以及处理事件

作用

  • 保持程序的持续运行
  • 监听处理App中的各种事件(触摸事件,定时器事件,selector事件)
  • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
  • 一次RunLoop循环负责绘制屏幕上所有的点

入口函数

  1. int main(int argc, char * argv[]) {
  2. @autoreleasepool {
  3. return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  4. }
    }

UIApplicationMain()此函数内部就启动了一个RunLoop,所以此函数一直没有返回,保持了程序的持续运行,这个默认启动的RunLoop是跟主线程相关的

RunLoop对象

  • Foundation框架下 : NSRunLoop (基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的)
  • Core Foundation框架下 : CFRunLoopRef (纯 C 函数的 API,所有这些 API 都是线程安全的)

RunLoop与线程

  • 每条线程有唯一的一个与之对应的RunLoop对象
  • 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要手动创建
  • RunLoop在第一次获取时创建,在线程结束时销毁
  1. NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];//主线程对应的RunLoop
  2. [NSRunLoop mainRunLoop];//当前线程对应的RunLoop
  3. currentRunloop.getCFRunLoop;//转化为CFRunLoop
  4.  
  5. CFRunLoopGetMain();
  6. CFRunLoopGetCurrent();
  7.  
  8. //开启一个子线程
  9. [[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start];
  10.  
  11. -(void)run {
  12.  
  13. //创建子线程对应的RunLoop,currentRunLoop 懒加载的
  14. [NSRunLoop currentRunLoop];
  15.  
  16. }

以下是苹果官方源码,通过分析源代码可以看出利用pthread作为全局字典中的key,并创建与之对应的RunLoop作为Value,RunLoop在我们获取的时候创建,不获取不创建,主线程的RunLoop在一开始就自动创建。线程与RunLoop是一一对应的关系

  1. CFRunLoopRef CFRunLoopGetMain(void) {
  2. CHECK_FOR_FORK();
  3. static CFRunLoopRef __main = NULL; // no retain needed
  4. if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
  5. return __main;
  6. }
  7.  
  8. CFRunLoopRef CFRunLoopGetCurrent(void) {
  9. CHECK_FOR_FORK();
  10. CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
  11. if (rl) return rl;
  12. return _CFRunLoopGet0(pthread_self());
  13. }
  14.  
  15. //全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
  16. static CFMutableDictionaryRef __CFRunLoops = NULL;
  17. //访问 Dictionary 时的锁
  18. static CFLock_t loopsLock = CFLockInit;
  19. //获取一个 pthread 对应的 RunLoop
  20. CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  21. //如果传进来的线程等于 0
  22. if (pthread_equal(t, kNilPthreadT)) {
  23. //当前线程等于主线程
  24. t = pthread_main_thread_np();
  25. }
  26. //给操作加锁
  27. __CFLock(&loopsLock);
  28. //如果当前RunLoop为空,创建。
  29. if (!__CFRunLoops) {
  30. __CFUnlock(&loopsLock);
  31. // 创建字典
  32. CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, , NULL, &kCFTypeDictionaryValueCallBacks);
  33. // 创建主线程
  34. CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
  35. // 保存主线程
  36. CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
  37.  
  38. if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
  39. CFRelease(dict);
  40. }
  41. CFRelease(mainLoop);
  42. __CFLock(&loopsLock);
  43. }
  44. // 从字典中获取当前线程的RunLoop
  45. CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  46. __CFUnlock(&loopsLock);
  47. if (!loop) {
  48. // 如果当前线程的runloop不存在,那么就为该线程创建一个对应的runloop
  49. CFRunLoopRef newLoop = __CFRunLoopCreate(t);
  50. __CFLock(&loopsLock);
  51. loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  52. // 把当前子线程和对应的runloop保存到字典中
  53. if (!loop) {
  54. CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
  55. loop = newLoop;
  56. }
  57. // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  58. __CFUnlock(&loopsLock);
  59. CFRelease(newLoop);
  60. }
  61. if (pthread_equal(t, pthread_self())) {
  62. _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
  63. if ( == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
  64. _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-), (void (*)(void *))__CFFinalizeRunLoop);
  65. }
  66. }
  67. return loop;
  68. }

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的状态改变。

  1. /* Run Loop Observer Activities */
  2.  
  3. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
  4.  
  5. kCFRunLoopEntry = (1UL << ),//即将进入Loop
  6.  
  7. kCFRunLoopBeforeTimers = (1UL << ),//即将处理Timer
  8.  
  9. kCFRunLoopBeforeSources = (1UL << ),//即将处理Source
  10.  
  11. kCFRunLoopBeforeWaiting = (1UL << ),//即将进入休眠
  12.  
  13. kCFRunLoopAfterWaiting = (1UL << ),//刚从休眠中唤醒
  14.  
  15. kCFRunLoopExit = (1UL << ),//即将退出Loop
  16.  
  17. kCFRunLoopAllActivities = 0x0FFFFFFFU
  18.  
  19. };
  1. -(void)addObserver{
  2.  
  3. /*
  4. 参数一:怎么分配存储空间
  5. 参数二:要监听的状态 kCFRunLoopAllActivities 所有的状态
  6. 参数三:是否要持续监听
  7. 参数四:优先级,总是传0
  8. 参数五:当状态改变时候的回调
  9. */
  10.  
  11. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, , ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  12.  
  13. switch (activity) {
  14. case kCFRunLoopEntry:
  15. NSLog(@"即将进入runloop");
  16. break;
  17. case kCFRunLoopBeforeTimers:
  18. NSLog(@"即将处理timer事件");
  19. break;
  20. case kCFRunLoopBeforeSources:
  21. NSLog(@"即将处理Sources事件");
  22. break;
  23. case kCFRunLoopBeforeWaiting:
  24. NSLog(@"即将进入睡眠");
  25. break;
  26. case kCFRunLoopAfterWaiting:
  27. NSLog(@"被唤醒");
  28. break;
  29. case kCFRunLoopExit:
  30. NSLog(@"runloop退出");
  31. break;
  32.  
  33. default:
  34. break;
  35. }
  36.  
  37. });
  38. //给runloop添加监听者
  39. /*
  40. 参数一:要监听哪个runloop
  41. 参数二:观察者
  42. 参数三:运行模式
  43. */
  44. CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  45. }

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占位模式下

  1. - (void)viewDidLoad {
  2.  
  3. [super viewDidLoad];
  4.  
  5. //1.创建定时器
  6.  
  7. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  8.  
  9. //2.将Timer添加到RunLoop中
  10.  
  11. //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
  12.  
  13. //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
  14.  
  15. [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
  16.  
  17. }
  18.  
  19. - (void)run {
  20.  
  21. NSLog(@"-------- %@",[NSThread currentThread]);
  22.  
  23. }

问题:在多线程开发中,耗时操作我们一般会放在子线程中执行,请问这种线程有什么特点?

实例:假如在上面的定时器的run方法中,执行一个耗时操作,此时会卡住主线程,拖动滑动控件会很不流畅,应该如何解决?

分析:子线程中默认不会开启RunLoop循环,所以子线程在执行完任务之后就会被回收

  1. - (void)viewDidLoad {
  2.  
  3. [super viewDidLoad];
  4.  
  5. dispatch_async(dispatch_get_global_queue(, ), ^{
  6.  
  7. //1.创建定时器
  8.  
  9. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  10.  
  11. //2.将Timer添加到RunLoop中
  12.  
  13. [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
  14.  
  15. //3.让RunLoop运行起来
  16.  
  17. [[NSRunLoop currentRunLoop] run];//死循环,后面的代码不会执行
  18.  
  19. NSLog(@"+++++++++");
  20.  
  21. });
  22.  
  23. }
  24.  
  25. - (void)run {
  26.  
  27. //耗时操作
  28.  
  29. [NSThread sleepForTimeInterval:1.0];
  30.  
  31. NSLog(@"-------- %@",[NSThread currentThread]);
  32.  
  33. }

或者:

  1. - (void)viewDidLoad {
  2.  
  3. [super viewDidLoad];
  4.  
  5. [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil];
  6.  
  7. }
  8.  
  9. -(void)time2{
  10.  
  11. //创建当前线程的RunLoop
  12.  
  13. NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
  14.  
  15. //该方法内部自动添加到RunLoop中,并且运行模式是默认模式
  16.  
  17. [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  18.  
  19. //开启RunLoop
  20.  
  21. [currentLoop run];
  22.  
  23. }
  24.  
  25. - (void)run {
  26.  
  27. //耗时操作
  28.  
  29. [NSThread sleepForTimeInterval:1.0];
  30.  
  31. NSLog(@"run ----- %@ ---- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
  32.  
  33. }

此博文会继续不断完善及更新关于涉及到runloop的知识,如果有理解不正确或者有涉及到runloop的应用实例,请留言吧,欢迎讨论

参考文章:http://blog.ibireme.com/2015/05/18/runloop/

iOS开发 - RunLoop理解的更多相关文章

  1. iOS开发SDWebImageOptions理解

    iOS开发SDWebImageOptions理解 原文 http://www.cnblogs.com/WJJ-Dream/p/5816750.html typedef NS_OPTIONS(NSUIn ...

  2. 0112.1——iOS开发之理解iOS中的MVC设计模式

    模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程 ...

  3. iOS开发之理解iOS中的MVC设计模式

    模型-视图-控制器(Model-View-Controller,MVC)是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程 ...

  4. iOS开发RunLoop学习:一:RunLoop简单介绍

    一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementatio ...

  5. iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题

    一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...

  6. iOS开发RunLoop

    最近处于离职状态,时间也多了起来,但是学习还是不能放松,今天总结一下RunLoop,RunLoop属于iOS系统层的东西,还是比较重要的. 一.什么是RunLoop 字面意思看是跑圈,也可以看作运行循 ...

  7. iOS开发-Runloop详解(简书)

    不知道大家有没有想过这个问题,一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件 ...

  8. 玩转iOS开发 - Runloop 具体解释

    Runloop 具体解释

  9. iOS开发RunLoop学习:三:Runloop相关类(source和Observer)

    一:RunLoop相关类: 其中:source0指的是非基于端口por,说白了也就是处理触摸事件,selector事件,source1指的是基于端口的port:是处理系统的一些事件 注意:创建一个Ru ...

随机推荐

  1. Android开发之onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法

    onMeasure()函数由包含这个View的具体的ViewGroup调用,因此值也是由其ViewGroup中传入的.子类View的这两个参数widthMeasureSpec, heightMeasu ...

  2. apache配置文件详解及虚拟主机的搭建

    1.404跳转: <IfModule dir_module>    DirectoryIndex index.php index.html /error.php</IfModule& ...

  3. Codeforces Round #374 (Div. 2) C. Journey —— DP

    题目链接:http://codeforces.com/contest/721/problem/C C. Journey time limit per test 3 seconds memory lim ...

  4. 未公开函数MessageBoxTimeOut 实现定时消息(ZT) MFC实现MessageBox自动消失

    http://www.blogjava.net/baicker/archive/2007/07/13/130072.html #include <windows.h> #include & ...

  5. dhclient命令

    语法:dhclient(选项)(参数) 选项0:指定dhcp客户但监听的端口号-d:总是以前台方式运行程序-q:安静模式,不打印任何错误的提示信息-r:释放ip地址 参数:网络接口:操作的网络接口 示 ...

  6. php排序方法之选择排序

    //选择排序法 $arr = array(3,55,45,2,67,76,6.7,-65,85,4); function selectSort($arr){ for ( $i=0; $i<cou ...

  7. log4j 配置文件详解

    [1]从零开始 a). 新建Java Project>>新建package>>新建java类: b). import jar包(一个就够),这里我用的是log4j-1.2.14 ...

  8. DDD领域驱动之干货 (一)

    说道DDD不得不说传统的架构与DDD的架构区别. 传统的架构不外乎就是三层,而在这三层里面又不断的细分,始终没有达到想要的效果,那么为什么当时还是采用三层. 当然在DDD没有提出的时候三层是大多数人的 ...

  9. Tomcat 安装之后,双击Tomcat.exe,无法运行成功,怎么办?

    Log形式多种多样,有的时候跟下面一样: 首先的解决方案就是:修改server.xml中所有的端口,因为不只8080端口可能被占用! 我在出问题的时候就修改8080端口.结果死活运行不了,纠结一些时间 ...

  10. c++ zlib(qt)压缩与解压缩

    #include <QtCore/QCoreApplication> #include "zlib.h" #include "stdio.h" #i ...