一、简介

CFRunLoopRef源码

RunLoop是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。

RunLoop的代码逻辑:详细解释请看这里

// 用DefaultMode启动
void CFRunLoopRun(void) {    /* DOES CALLOUT */int32_t result;do {result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);CHECK_FOR_FORK();} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  1. 这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
  2. RunLoop管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

二、RunLoop的深入分析

1. 从程序入口main函数开始

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

程序主线程一开始,就会一直跑,那么猜想其内部一定是开启了一个和主线程对应的RunLoop并且可以看出函数返回的是一个int返回值的 UIApplicationMain()函数

2. 我们继续深入UIApplicationMain函数

UIKIT_EXTERN int UIApplicationMain
(int argc,
char *argv[],
NSString * __nullable principalClassName,NSString * __nullable delegateClassName
);

我们发现它返回的是一个int类型的值,那么我们对main函数做一些修改:

int main(int argc, char * argv[]) {@autoreleasepool {NSLog(@"开始");int re = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));NSLog(@"结束");return re;}
}

运行程序,我们发现只会打印开始,并不会打印结束,这再次说明在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。

3. 继续学习CFRunLoopRef

RunLoop对象包括Fundation中的NSRunLoop对象和CoreFoundation中的CFRunLoopRef对象。因为Fundation框架是基于CoreFoundation的封装,因此我们学习RunLoop还是要研究CFRunLoopRef 源码。

获取RunLoop对象

//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

1. 主线程获取CFRunLoopRef源码

   // 创建字典CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

2. 创建与子线程相关联的CFRunLoopRe源码苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。

// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {OSSpinLockLock(&loopsLock);if (!loopsDic) {// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。loopsDic = CFDictionaryCreateMutable();CFRunLoopRef mainLoop = _CFRunLoopCreate();CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);}// 直接从 Dictionary 里获取。CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));if (!loop) {// 取不到时,创建一个loop = _CFRunLoopCreate();CFDictionarySetValue(loopsDic, thread, loop);// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);}OSSpinLockUnLock(&loopsLock);return loop;
}
CFRunLoopRef CFRunLoopGetMain() {return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {return _CFRunLoopGet(pthread_self());
}

3. CFRunloopRef与线程之间的关系

  1. 首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。
  2. CFRunLoopRef源码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。[NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。

. 总结来说. CFRunloopRef与线程之间的关系

  1. 线程在处理完自己的任务后一般会退出,为了实现线程不退出能够随时处理任务的机制被称为EventLoop,node.js 的事件处理,windows程序的消息循环,iOS、OSX的RunLoop都是这种机制。
  2. 线程和RunLoop是一一对应的,关系保存在全局的字典里。在主线程中,程序启动时,系统默认添加了有kCFRunLoopDefaultMode 和 UITrackingRunLoopMode两个预置Mode的RunLoop,保证程序处于等待状态,如果接收到来自触摸事件等,就会执行任务,否则处于休眠中。
  3. 线程创建时并没有RunLoop,(主线程除外),RunLoop不能创建,只能主动获取才会有。RunLoop的创建是在第一次获取时,RunLoop的销毁是发生在线程结束时。只能在一个线程中获取自己和主线程的RunLoop。

Core Foundation中关于RunLoop的5个类

CFRunLoopRef  //获得当前RunLoop和主RunLoop
CFRunLoopModeRef  //运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef  //事件源,输入源
CFRunLoopTimerRef //定时器时间
CFRunLoopObserverRef //观察者

CFRunLoopModeRef详细内容请点击这里1.简介:

每个CFRunLoopRef 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:

CFRunLoopRef获取Mode的接口:

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);

2.CFRunLoopMode的类型

  1. kCFRunLoopDefaultModeApp的默认Mode,通常主线程是在这个Mode下运行

  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes:这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

3.CFRunLoopMode 和 CFRunLoop 的结构

struct __CFRunLoopMode {CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"CFMutableSetRef _sources0;    // Set CFMutableSetRef _sources1;    // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers;    // Array ...
};struct __CFRunLoop {CFMutableSetRef _commonModes;     // Set CFMutableSetRef _commonModeItems; // Set CFRunLoopModeRef _currentMode;    // Current Runloop ModeCFMutableSetRef _modes;           // Set ...
};

CFRunLoopSourceRef

  1. 是事件产生的地方。Source有两个版本:Source0 和 Source1。
  2. Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  3. Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.

CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。

我们直接来看代码,给RunLoop添加监听者,监听其运行状态:

 //创建监听者/*第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态第三个参数 Boolean repeats:YES:持续监听 NO:不持续第四个参数 CFIndex order:优先级,一般填0即可第五个参数 :回调 两个参数observer:监听者 activity:监听的事件*//*所有事件typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL <&lt; 0),   //   即将进入RunLoopkCFRunLoopBeforeTimers = (1UL <&lt; 1), // 即将处理TimerkCFRunLoopBeforeSources = (1UL <&lt; 2), // 即将处理SourcekCFRunLoopBeforeWaiting = (1UL <&lt; 5), //即将进入休眠kCFRunLoopAfterWaiting = (1UL <&lt; 6),// 刚从休眠中唤醒kCFRunLoopExit = (1UL <&lt; 7),// 即将退出RunLoopkCFRunLoopAllActivities = 0x0FFFFFFFU};*/CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {switch (activity) {case kCFRunLoopEntry:NSLog(@"RunLoop进入");break;case kCFRunLoopBeforeTimers:NSLog(@"RunLoop要处理Timers了");break;case kCFRunLoopBeforeSources:NSLog(@"RunLoop要处理Sources了");break;case kCFRunLoopBeforeWaiting:NSLog(@"RunLoop要休息了");break;case kCFRunLoopAfterWaiting:NSLog(@"RunLoop醒来了");break;case kCFRunLoopExit:NSLog(@"RunLoop退出了");break;default:break;}});// 给RunLoop添加监听者/*第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop第二个参数 CFRunLoopObserverRef observer 监听者第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态*/CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);/*CF的内存管理(Core Foundation)凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次releaseGCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了*/CFRelease(observer);

iOS RunLoop详解相关推荐

  1. IOS UIView详解

    文章目录 IOS UIView详解 1.官方类分析 2. UIView 常用的属性 2.1 UIView的圆角加阴影效果的实现 2.2 UIView 属性 2.2.1 UIView 几何属性 2.2. ...

  2. FreeEIM 来点新知识iOS UIScrollView详解

     老程序员FreeEIM 来点新知识iOS UIScrollView详解 UIScrollView 顾名思义也知道这个是和滚动相关的控件,在Android开发时遇到过ScrollView,当内容的 ...

  3. iOS绘图详解-多种绘图方式、裁剪、滤镜、移动、CTM

    iOS绘图详解 摘要: Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎.它提供了低级别.轻量级.高保真度的2D渲染.该框架可以用于基于路径的 绘 ...

  4. iOS疯狂详解之AFNetworking图片缓存问题

    AFNetworking网络库已经提供了很好的图片缓存机制,效率是比较高的,但是我发现没有直接提供清除缓存的功能,可项目通常都需要添加 清除功能的功能,因此,在这里我以UIImageView+AFNe ...

  5. iOS多线程详解:实践篇

    iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI.在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于在iOS中除了主线程,其他子线程是独立 ...

  6. iOS疯狂详解之开源库

    youtube下载神器:https://github.com/rg3/youtube-dl vim插件:https://github.com/Valloric/YouCompleteMe vim插件配 ...

  7. [iOS] 国际化详解

    PS:修改设备系统语言方法 设置 -> 通用 -> 语言与地区 -> iPhone 语言 Settings -> General -> Language & Re ...

  8. iOS模式详解runtime面试工作

    简书:http://www.jianshu.com/p/19f280afcb24 对于从事 iOS 开发人员来说,所有的人都会答出「runtime 是运行时」,什么情况下用runtime?,大部分人能 ...

  9. UE4 IOS打包详解

    写在前面:因为是详解,所以可能写的有可能啰嗦,也有可能有些步骤是你经历过的,那么请忽略它,向下寻找可能的答案,如果没能解决你的问题,那么对此感到很抱歉,没能帮到你,欢迎你给我邮件: bluecode6 ...

最新文章

  1. MPB:北大口腔陈峰、陈智滨等-​口腔微生物组研究主要取样部位及方法
  2. 做移动应用使用地图API时需要注意的问题
  3. mysql错误18456_【问题解决】SQL2008 SQL Server身份认证方式登录失败(错误18456)解决方法图解...
  4. c语言程序设计编程解读,C语言程序设计第三次实验报告解读
  5. techempower之Plaintext上7百万RPS
  6. Java开发利器:IntelliJ IDEA的安装、配置与使用
  7. 7-9 包装机 (25 分)
  8. SmartSql使用教程(1)——初探,建立一个简单的CURD接口服务
  9. flask第七篇——URL与视图函数的映射
  10. pcie握手机制_图解PCIE原理(从软件角度)
  11. 非线性控制1.0——模糊控制理论基础
  12. 办公软件 excle word 技巧 教程 电子书 免费 下载
  13. php 去零取整,php取整的几种方法
  14. Nginx部署ssl安全证书(腾讯云DV证书)
  15. 牛客练习赛34-C.(前缀和)
  16. 独自美丽-西西里的美丽传说『by berta』
  17. 【软件测试】什么样的项目适合做自动化测试?自动化测试有需要那些技术?
  18. 第一个安卓app应用的开发--环境配置和第一项目创建
  19. Linux中的Seq
  20. 如何进入mysql数据库

热门文章

  1. 回调函数在C/C++中的使用
  2. avue validate 变为不可编辑_排版技巧——如何用 Word 编辑参考文献
  3. 7-5 二分法求多项式单根 (20分)
  4. $().html()对ie9无效,不注意这点,\9和\0就可能对hack IE11\IE9\IE8无效
  5. c语言函数库哪里keyk,[精品]C语言库函数(字母G-K)-教案.doc
  6. 【css】padding 和 margin的区别
  7. 作业六:图像编码相关概念
  8. js页面跳转或重定向
  9. iOS_Development~ 添加 / 隐藏 UITabBar 右上角的小红点
  10. 基于【CentOS-7+ Ambari 2.7.0 + HDP 3.0】搭建HAWQ数据仓库01 —— 准备环境,搭建本地仓库,安装ambari...