做了两年多的IOS开发,对IOS和Objective-C深层次的了解还十分有限,大多还停留在会用API的级别,这是件挺可悲的事情。想学好一门语言还是需要深层次的了解它,这样才能在使用的时候得心应手,出现各种怪异的问题时不至于不知所措。废话少说,进入今天的正题。

我的一个iOS交流群656315826,不管是小白还是大牛,都欢迎进群交流,大家一起学习进步!放眼全球,iOS开发表现抢眼

废话少说,进入今天的正题。

RunLoop 的概念

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:

这种模型通常被称作Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。

所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回

RunLoop 与线程的关系

首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。

苹果不允许直接创建 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());
复制代码

}

从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

NSRunLoop 和 CFRunLoopRef

我们不能再一个线程中去操作另外一个线程的run loop对象,那很可能会造成意想不到的后果。不过幸运的是CoreFundation中的不透明类CFRunLoopRef是线程安全的,而且两种类型的run loop完全可以混合使用。Cocoa中的NSRunLoop类可以通过实例方法:

  • (CFRunLoopRef)getCFRunLoop

获取对应的CFRunLoopRef类,来达到线程安全的目的。

 CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
复制代码

RunLoop 对外接口/ RunLoop 的几个类

在 CoreFoundation 里面关于 RunLoop 有5个类:

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef

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

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

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

RunLoop 内部逻辑

可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

RunLoop 的实际应用举例

AFNetworking

AFULConnectionOPeration这个类是基于 NSURLConnection 构建的,其希望能在后台线程接收 Delegate 回调。为此 AFNetworking 单独创建了一个线程,并在这个线程中启动了一个 RunLoop:

RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。 当需要这个后台线程执行任务时,AFNetworking 通过调用 [NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop 中。

最后

大家如果想看更多干货可以帮忙点个关注跟收藏,其实作为一个开发者,有一个氛围良好的学习交流圈子很重要,大家可以加入我的一个iOS交流群656315826,大家一起交流成长!

转载于:https://juejin.im/post/5a6de05e51882573432d6a21

手把手教你如何成为大牛相关推荐

  1. python手机版做小游戏代码大全-Python大牛手把手教你做一个小游戏,萌新福利!...

    原标题:Python大牛手把手教你做一个小游戏,萌新福利! 引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规 ...

  2. 手把手教你简单接入微信SDK

    就看微信现在这么火的样子,如果你的APP不接入微信的SDK好像就有点脱离了时代大车轮一样.一个成功的APP,不单单凭借着一个好的想法,一个好的功能,最主要还是用户量.用户量就好像是水,我们的APP就一 ...

  3. linux 模块化编译,手把手教Linux驱动1-模块化编程 module

    大家好,从本篇起,一口君将手把手教大家如何来学习Linux驱动,预计会有20篇关于驱动初级部分知识点.本专题会一直更新,有任何疑问,可以留言或者加我微信. Linux的开发者,遍布世界各地,他们相互之 ...

  4. 从0到1,手把手教你如何使用哈工大NLP工具——PyLTP

    导读:此文是作者基于 Python 构建知识图谱的系列实践教程,具有一定创新性和实用性.文章前半部分内容先介绍哈工大 pytltp 工具,包括安装过程.中文分词.词性标注和实体识别的一些基本用法:后半 ...

  5. 手把手教你从0到1进行Java项目实践

    手把手教你从0到1进行Java项目实践 虽说工作就是简单的事情重复做,但不是所有简单的事你都能有机会做的. 我们平日工作里,大部分时候都是在做修修补补的工作,而这也是非常重要的.做好修补工作,做好优化 ...

  6. 手把手教你训练一个秒杀科比的投篮AI,不服来练 | 附开源代码

    原作:Abe Haskins 安妮 编译整理 量子位 出品 | 公众号 QbitAI 在这篇教程中,谷歌工程师Abe Haskins用简洁易懂的语言,教你用Unity3D和TensorFlow生产一只 ...

  7. 报名 | NVIDIA线下交流会:手把手教你搭建TensorFlow Caffe深度学习服务器

    7月21日(周六)下午14:30,量子位与NVIDIA英伟达开发者社区联合举办线下交流会,拥有丰富一线开发经验的NVIDIA开发者社区经理Ken He,将手把手教你搭建TensorFlow & ...

  8. 手把手教你使用 VuePress 搭建个人博客

    手把手教你使用 VuePress 搭建个人博客 有阅读障碍的同学,可以跳过第一至四节,下载我写好的工具包: git clone https://github.com/zhangyunchencc/vu ...

  9. Python之手把手教你用JS逆向爬取网易云40万+评论并用stylecloud炫酷词云进行情感分析

    本文借鉴了@平胸小仙女的知乎回复 https://www.zhihu.com/question/36081767 写在前面: 文章有点长,操作有点复杂,需要代码的直接去文末即可.想要学习的需要有点耐心 ...

最新文章

  1. iOS访问系统日历 添加提醒事件
  2. 汇编语言第二课作业-实验1
  3. Android Studio使用Lint进行代码检查
  4. C++ 指向常量的指针与指针类型的常量
  5. linux 分区 var,Ubuntu下移动/var目录到单独分区后出现的一些问题
  6. MySQL Merge引擎实现分表
  7. Android 手电筒源码
  8. php mk的支持扩展,Linux部署Redis及PHP-redis扩展
  9. 动态改变eachers图表高_让你的Excel图表动起来
  10. 海龟交易策略要点总结
  11. 【tf.keras】tf.keras模型复现
  12. Genius‘s Gambit(构造)
  13. 概率图模型之贝叶斯网络的理解与应用
  14. 【信息系统项目管理师】论文素材大汇总
  15. 录制软件obs的使用方法
  16. Java是什么?Java能干什么工作?
  17. android锁屏界面布局修改,android 修改锁屏界面
  18. 瑞吉外卖项目重难点及易错点知识点总结
  19. hotmail邮箱在Outlooknbsp;2010中…
  20. win10 旗舰版 系统激活出现 在连接到本地注册表时出现错误0x80041002 提示信息 解决方法...

热门文章

  1. Storm和MR及Spark Streaming的区别
  2. (0106)iOS开发之iOS13 适配
  3. Mac-使用技巧之快速新建txt文本
  4. AcWing P164 可达性统计 题解
  5. 使用Donut Caching和Donut Hole Caching在ASP.NET MVC应用中缓存页面
  6. hdu 1520 没有上司的晚会
  7. notifyDataSetInvalidated()跟notifyDataSetChanged()的区别
  8. Windows 编程之 对话框总结
  9. libevent 安装异常
  10. [原创]K8域控植入脚本生成器(内网渗透/RPC不可用解决方案)