http://www.jianshu.com/p/0be6be50e461

基本概念

进程

进程是指在系统中正在运行的一个应用程序,而且每个进程之间是独立的,它们都运行在其专用且受保护的内存空间内,比如同时打开迅雷、Xcode,系统就会分别启动两个进程。

线程

一个人进程如果想要执行任务,必须得有至少一条线程,进程的所有任务都会在线程中执行,比如使用网易云音乐播放音乐,使用迅雷下载电影,都需要在线程中执行。

主线程

iOS 程序运行后,系统会默认开启一条线程,称为“主线程”或者“UI 线程”,主线程是用来显示/刷新 UI 界面,处理 UI 事件的。


简介

运行循环、跑圈

总结下来,RunLoop 的作用主要体现在三方面:

  1. 保持程序的持续运行
  2. 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
  3. 节省CPU资源,提高程序性能:该做事的时候做事,该休息的时候休息

就是说,如果没有 RunLoop 程序一运行就结束了,你根本不可能看到持续运行的 app。

iOS中有2套API访问和使用RunLoop

  • Foundation:NSRunLoop
  • Core Foundation: CFRunLoopRef

NSRunLoop是基于CFRunLoopRef的一层OC包装,因此我们需要研究CFRunLoopRef层面的API(Core Foundation层面)

关于 RunLoop 的源码请看这里


RunLoop与线程

源码中,关于创建线程的核心代码如下:

  1. // should only be called by Foundation
  2. // t==0 is a synonym for "main thread" that always works
  3. CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  4. if (pthread_equal(t, kNilPthreadT)) {
  5. t = pthread_main_thread_np();
  6. }
  7. __CFLock(&loopsLock);
  8. if (!__CFRunLoops) { // 如果没有线程,则要创建线程
  9. __CFUnlock(&loopsLock);
  10. // 创建一个可变字典
  11. CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
  12. // 将主线程放进去,创建 RunLoop(也就是说,创建哪个线程的 RunLoop 需要将线程作为参数传入)
  13. CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
  14. // 将主线程的 RunLoop 和主线程以 key/value 的形式保存。
  15. // 因此由此可以看出,一条线程和一个 RunLoop 是一一对应的
  16. CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
  17. if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
  18. CFRelease(dict);
  19. }
  20. CFRelease(mainLoop);
  21. __CFLock(&loopsLock);
  22. }
  23. // 当你输入 cunrrentRunLoop 时,会通过当前线程这个 key,在字典中寻找对应的 RunLoop
  24. CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  25. __CFUnlock(&loopsLock);
  26. // 如果没有在字典中找到
  27. if (!loop) {
  28. // 则重新创建一个 RunLoop
  29. CFRunLoopRef newLoop = __CFRunLoopCreate(t);
  30. __CFLock(&loopsLock);
  31. // 然后将 RunLoop 和线程以 key/value 的形式保存
  32. // 再一次验证了 RunLoop 和 key 是一一对应的
  33. loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  34. if (!loop) {
  35. CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
  36. loop = newLoop;
  37. }
  38. // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  39. __CFUnlock(&loopsLock);
  40. CFRelease(newLoop);
  41. }
  42. if (pthread_equal(t, pthread_self())) {
  43. _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
  44. if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
  45. _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
  46. }
  47. }
  48. return loop;
  49. }

程序启动时,系统会自动创建主线程的 RunLoop

  • 每一条线程都有唯一的一个与之对应的RunLoop对象
  • 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要手动创建
  • RunLoop在第一次获取时创建,在线程结束时销毁

代码:

  1. // 获取当前的线程的RunLoop对象,注意RunLoop是懒加载,currentRunLoop时会自动创建对象
  2. [NSRunLoop currentRunLoop];
  3. // 获取主线程的RunLoop对象
  4. [NSRunLoop mainRunLoop];
  5. // 如果是 CF 层面
  6. CFRunLoopGetCurrent();
  7. CFRunLoopGetMain();

RunLoop相关类

通过:

  1. NSLog(@"%@", [NSRunLoop mainRunLoop]);

可以对 RunLoop 内部一览无余

Core Foundation中关于RunLoop的5个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopObserverRef

 

RunLoop 想要跑起来,必须有 Mode 对象支持,而 Mode 里面必须有
(NSSet *)Source(NSArray *)Timer ,源和定时器。

至于另外一个类(NSArray *)observer是用于监听 RunLoop 的状态,因此不会激活RunLoop。

CFRunLoopModeRef

CFRunLoopModeRef 代表 RunLoop 的运行模式

每个 RunLoop 都包含若干个 Mode ,每个 Mode 又包含若干个 Source/Timer/Observer,每次 RunLoop 启动时,只能指定其中一个 Mode,这个 Mode 被称作CurrentMode,如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入,这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响(可以通过切换 Mode,完成不同的 timer/source/observer)。

  1. [NSRunLoop currentRunLoop].currentMode; // 获取当前运行模式
  2. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

系统默认注册了5个Mode:

  • NSDefaultRunLoopMode:App 的默认 Mode,通常主线程是在这个 Mode 下运行(默认情况下运行)
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响(操作 UI 界面的情况下运行)
  • UIInitializationRunLoopMode:在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到(绘图服务)
  • NSRunLoopCommonModes:这是一个占位用的 Mode,不是一种真正的 Mode (RunLoop无法启动该模式,设置这种模式下,默认和操作 UI 界面时线程都可以运行,但无法改变 RunLoop 同时只能在一种模式下运行的本质)

下面主要区别 NSDefaultRunLoopMode 和 UITrackingRunLoopMode 以及 NSRunLoopCommonModes。请看以下代码:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  4. // 在默认模式下添加的 timer 当我们拖拽 textView 的时候,不会运行 run 方法
  5. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  6. // 在 UI 跟踪模式下添加 timer 当我们拖拽 textView 的时候,run 方法才会运行
  7. [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
  8. // timer 可以运行在两种模式下,相当于上面两句代码写在一起
  9. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  10. }
  11. - (void)run
  12. {
  13. NSLog(@"--------run");
  14. }
  1. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  2. [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES modes:@[UITrackingRunLoopMode]];
  3. [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES modes:@[NSRunLoopCommonModes]];

CFRunLoopTimerRef

  • CFRunLoopTimerRef 是基于事件的触发器
  • CFRunLoopTimerRef 基本上就是 NSTimer,它受 RunLoop的Mode 影响

创建 Timer 有两种方式,下面的这种方式必须手动添加到 RunLoop 中去才会被调用

  1. // 这种方式创建的timer 必须手动添加到RunLoop中去才会被调用
  2. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(time) userInfo:nil repeats:YES];
  3. [[NSRunLoop currentRunLoop] addTimer:timer
  4. forMode:NSDefaultRunLoopMode];
  5. // 同时让RunLoop跑起来
  6. [[NSRunLoop currentRunLoop] run];

而通过 scheduledTimer 创建 Timer 一开始就会自动被添加到当前线程并且以
NSDefaultRunLoopMode 模式运行起来,代码如下:

  1. [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  2. /*
  3. 注意:调用了 scheduledTimer 返回的定时器,已经自动被添加到当前
  4. runLoop 中,而且是 NSDefaultRunLoopMode ,想让上述方法起作用,
  5. 必须先让添加了上述 timer的RunLoop 对象 run 起来,通过
  6. scheduledTimerWithTimeInterval 创建的 timer 可以通过以下方法修改 mode
  7. */
  8. [[NSRunLoop currentRunLoop] addTimer:timer2 forMode:UITrackingRunLoopMode];

注意: GCD的定时器不受RunLoop的Mode影响

  1. CADisplayLink *display = [CADisplayLink displayLinkWithTarget:self selector:@selector(run)];
  2. /*
  3. 注意:CADisplayLink ,也是在 Runloop 下运行的,
  4. 有一个方法可以将CADisplayLink 对象添加到一个 Runloop 对象中去
  5. */
  6. [display addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

CFRunLoopSourceRef

CFRunLoopSourceRef 其实是事件源(输入源)

按照官方文档,Source的分类

  • Port-Based Sources:基于端口的:跟其他线程进行交互的,Mac内核发过来一些消息
  • Custom Input Sources:自定义输入源
  • Cocoa Perform Selector Sources(self performSelector:...)

按照函数调用栈,Source的分类

  • Source0:非基于Port的(触摸事件、按钮点击事件)
  • Source1:基于Port的,通过内核和其他线程通信,接收分发系统事件
    1. (触摸硬件,通过 Source1 接收和分发系统事件到 Source0 处理)

为了搞清楚,Source 是如何通过函数调用栈来传递事件的,我们做如下实验:

 

我们可以看到,从程序启动 start 开始,函数调用栈在监听到事件点击后,会一路往下,一直到 -buttonClick: 方法,中间会经过 CFRunLoopSource0 ,这说明我们的按钮点击事件是属于 Source0 的。

而 Source1 是基于 Port 的,就是说,Source1 是和硬件交互的,触摸首先在屏幕上被包装成一个 event 事件,再通过 Source1 进行分发到 Source0,最后通过 Source0 进行处理。

 

CFRunLoopObserverRef

CFRunLoopObserverRef 是观察者,能够监听 RunLoop 的状态改变,主要监听以下几个时间节点:

  1. /* Run Loop Observer Activities */
  2. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity)
  3. {
  4. kCFRunLoopEntry = (1UL << 0), // 1 即将进入 Loop
  5. kCFRunLoopBeforeTimers = (1UL << 1), // 2 即将处理 Timer
  6. kCFRunLoopBeforeSources = (1UL << 2), // 4 即将处理 Source
  7. kCFRunLoopBeforeWaiting = (1UL << 5), // 32 即将进入休眠
  8. kCFRunLoopAfterWaiting = (1UL << 6), // 64 刚从休眠中唤醒
  9. kCFRunLoopExit = (1UL << 7), // 128 即将退出 Loop
  10. kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听所有事件
  11. };
  1. // 1.创建观察者 监听 RunLoop
  2. // 参1: 有个默认值 CFAllocatorRef :CFAllocatorGetDefault()
  3. // 参2: CFOptionFlags activities 监听RunLoop的活动 枚举 见上面
  4. // 参3: 重复监听 Boolean repeats YES
  5. // 参4: CFIndex order 传0
  6. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  7. // 该方法可以在添加timer之前做一些事情, 在添加source之前做一些事情
  8. NSLog(@"%zd", activity);
  9. });
  10. // 2.添加观察者,监听当前的RunLoop对象
  11. CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  12. // CF层面的东西 凡是带有create、copy、retain等字眼的函数在CF中要进行内存管理
  13. CFRelease(observer);

通过打印可以观察的 RunLoop 的状态

 

补充:在进入第一个阶段前,会先判断当前 RunLoop 空不空, 如果是空的 直接来到10阶段!


RunLoop的应用

NSTimer

需求 让定时器 在其他线程开启

  1. NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
  2. // 这种方式创建的timer 必须手动添加到Runloop中去才会被调用
  3. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(time) userInfo:nil repeats:YES];
  4. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  5. // 同时让RunLoop跑起来
  6. [[NSRunLoop currentRunLoop] run];
  7. }];
  8. [[[NSOperationQueue alloc] init] addOperation:block];
  9. [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  10. [[NSRunLoop currentRunLoop] run];
  11. [[NSRunLoop currentRunLoop] addTimer:timer2 forMode:UITrackingRunLoopMode];

ImageView:显示performSelector

需求
有时候,用户拖拽scrollView的时候,mode:UITrackingRunLoopMode,显示图片,如果图片很大,会渲染比较耗时,造成不好的体验,因此,设置当用户停止拖拽的时候再显示图片,进行延迟操作

  • 方法1:设置scrollView的delegate 当停止拖拽的时候做一些事情
  • 方法2:使用performSelector 设置模式为default模式 ,则显示图片这段代码只能在RunLoop切换模式之后执行
  1. // 加载比较大的图片时,
  2. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  3. {
  4. // inModes 传入一个 mode 数组,这句话的意思是
  5. // 只有在 NSDefaultRunLoopMode 模式下才会执行 seletor 的方法显示图片
  6. [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"avater"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
  7. }

效果为:当用户点击之后,下载图片,但是图片太大,不能及时下载。这时用户可能会做些其他 UI 操作,比如拖拽,但是如果用户正在拖拽浏览其他的东西时,图片下载完毕了,此时如果要渲染显示,会造成不好的用户体验,所以当用户拖拽完毕后,显示图片。

这是因为,用户拖拽,处于 UITrackingRunLoopMode 模式下,所以图片不会显示。

常驻线程

需求:
搞一个线程一直存在,一直在后台做一些操作 比如监听某个状态, 比如监听是否联网。

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. // 需求:搞一个线程一直不死,一直在后台做一些操作 比如监听某个状态, 比如监听是否联网。
  4. // 需要在线程中开启一个RunLoop 一个线程对应一个RunLoop 所以获得当前RunLoop就会自己创建RunLoop
  5. NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run2) object:nil];
  6. self.thread = thread;
  7. [thread start];
  8. }
  9. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  10. {
  11. [self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:NO];
  12. }
  13. - (void)run2
  14. {
  15. NSLog(@"----------");
  16. /*
  17. * 创建RunLoop,如果RunLoop内部没有添加任何Source Timer
  18. * 会直接退出循环,因此需要自己添加一些source才能保持RunLoop运转
  19. */
  20. [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
  21. // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  22. [[NSRunLoop currentRunLoop] run];
  23. NSLog(@"-----------22222222");
  24. }

从 RunLoop 的源码看来,如果一个 RunLoop 中没有添加任何的 Source Timer,会直接退出循环。

自动释放池

RunLoop循环时,在进入睡眠之前会清掉自动释放池,并且创建一个新的释放池,用于内部变量的销毁。

在子线程开RunLoop的时候一定要自己写一个@autoreleasepool,一个RunLoop对应一条线程,自动释放吃是针对当前线程里面的对象。

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(excute) object:nil];
  4. self.thread = thread;
  5. [thread start];
  6. }
  7. - (void)excute
  8. {
  9. @autoreleasepool {
  10. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(text) userInfo:nil repeats:YES];
  11. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  12. [[NSRunLoop currentRunLoop] run];
  13. }
  14. }

这样保证了内存安全。

文本代码:RunLoop

文/Ammar(简书作者)
原文链接:http://www.jianshu.com/p/0be6be50e461
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

RunLoop运行循环机制的更多相关文章

  1. iOS - OC RunLoop 运行循环/消息循环

    1.RunLoop 1)运行循环: 运行循环在 iOS 开发中几乎不用,但是概念的理解却非常重要. 同一个方法中的代码一般都在同一个运行循环中执行,运行循环监听 UI 界面的修改事件,待本次运行循环结 ...

  2. iOS Runloop 消息循环

    介绍 Runloop是一种事件监听循环,可以理解成一个while死循环,监听到事件就起来,没有就休息. Runloop可以在不同模式下进行切换,iOS有五种模式,其中UIInitializationR ...

  3. 运行循环 - RunLoop

    1.RunLoop简介 1.1 什么是RunLoop 简单来说就是:运行循环,可以理解成一个死循环,一直在运行. RunLoop实际上就是一个对象,这个对象用来处理程序运行过程中出现的各种事件(触摸. ...

  4. [Spark内核] 第35课:打通 Spark 系统运行内幕机制循环流程

    本课主题 打通 Spark 系统运行内幕机制循环流程 引言 通过 DAGScheduelr 面向整个 Job,然后划分成不同的 Stage,Stage 是從后往前划分的,执行的时候是從前往后执行的,每 ...

  5. 打通 Spark 系统运行内幕机制循环流程

    本课主题 打通 Spark 系统运行内幕机制循环流程 引言 通过 DAGScheduelr 面向整个 Job,然后划分成不同的 Stage,Stage 是从后往前划分的,执行的时候是從前往后执行的,每 ...

  6. JavaScript 运行机制:Event事件循环机制

    JavaScript Event事件循环机制 JS是单线程的,浏览器只分配一个主线程给JS.一次只能执行一个任务,当前任务执行完后在可以执行下一个任务.任务多时,就会形成任务队列排队等待执行.但是非常 ...

  7. iOS开发 底层抛析运行循环—— RunLoop

    http://blog.csdn.net/zc639143029/article/details/50012527 一.RunLoop基本概念 概念:程序的运行循环,通俗的来说就是跑圈. 1. 基本作 ...

  8. 【运行机制】 JavaScript的事件循环机制总结 eventLoop

    0.从个例子开始 //code-01 console.log(1) setTimeout(() => { console.log(2); }); console.log(3); 稍微有点前端经验 ...

  9. Android Handler 消息循环机制

    前言 一问起Android应用程序的入口,很多人会说是Activity中的onCreate方法,也有人说是ActivityThread中的静态main方法.因为Java虚拟机在运行的时候会自动加载指定 ...

随机推荐

  1. JavaScript学习笔记之CSS-DOM

    HTML负责结构层,网页的结构层由HTML或者XHTML之类的标记语言负责构建 CSS负责表示层,描述页面内容应该如何呈现. JavaScript负责行为层,负责内容应该如何响应事件这一问题. 能利用 ...

  2. 在vue中使用handsontable

    1.使用npm安装 npm install handsontable @handsontable/vue 2.定义结构 <hot-table :settings="hotSetting ...

  3. NX二次开发-bat脚本文件切换NX的环境变量(NX路径和语言)

    路径环境变量切换到NX9.bat @echo off setx /M UGII_BASE_DIR "D:\Program Files\Siemens\NX 9.0" ------- ...

  4. NX二次开发-NXOPEN导出STEP Step214Creator *step214Creator1;

    没有什么可以看的,NXOPEN直接录制一下导出STEP就可以了.录制出来自己挑需要的代码拿过来改一下. NX9+VS2012 #include <NXOpen/Part.hxx> #inc ...

  5. js的线程和同步异步以及console.log机制

    项目上线了,闲下来就写写东西吧.积累了好多东西都没有做笔记~挑几个印象深刻的记录一下吧. js的同步异步以及单线程问题: 都知道单线程是js的一大特性.但是通常io(ajax获取服务器数据).用户/浏 ...

  6. [JZOJ 5129] 字符串

    题意:统计本质不同的串的个数. 思路: 显然后缀自动机,对于每个串建一个\(SAM\)统计即可. #include <bits/stdc++.h> using namespace std; ...

  7. c# ToString()格式大全(转)

    stringstr1 =string.Format("{0:N1}",56789);               //result: 56,789.0stringstr2 =str ...

  8. PAT_A1079#Total Sales of Supply Chain

    Source: PAT A1079 Total Sales of Supply Chain (25 分) Description: A supply chain is a network of ret ...

  9. 转: div:给div加滚动条 div的滚动条设置

    div 的滚动条问题: 两种方法: 一. <div style=" overflow:scroll; width:400px; height:400px;”></div&g ...

  10. rmReport 自适应行高(自动行高)

    这个问题 1.先中主项数据--属性--stretched(伸展):true 选中主项数据中的所有列--属性--其他属性--自动折行 --伸展