分析IOS RunLoop的事件循环机制

在RunLoop启动之后会发送一个通知,来告知观察者

将要处理Timer/Source0事件这样一个通知的发送

处理Source0事件

如果有Source1要处理,这时会通过一个go to语句的实现来进行代码逻辑的跳转,处理唤醒是收到的消息

如果没有Source1要处理,线程就将要休眠,同时发送一个通知,告诉观察者

然后线程进入一个用户态到内核态的切换,休眠,然后等待唤醒,唤醒的条件大约包括三种:

1、Source1

2、Timer事件

3、外部手动唤醒

线程刚被唤醒之后也要发送一个通知告诉观察者,然后处理唤醒时收到的消息

回到将要处理Timer/Source0事件这样一个通知的发送

然后再次进行上面步骤,这就是一个RunLoop的事件循环机制

内部代码逻辑整理如下:

/// 用DefaultMode启动
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

    /// 首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    /// 如果mode里没有source/timer/observer, 直接返回。
    if (__CFRunLoopModeIsEmpty(currentMode)) return;

    /// 1. 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

    /// 内部函数,进入loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {

            /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
            /// 执行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);

            /// 4. RunLoop 触发 Source0 (非port) 回调。
            sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
            /// 执行被加入的block
            __CFRunLoopDoBlocks(runloop, currentMode);

            /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
            if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }

            /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
            if (!sourceHandledThisLoop) {
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
            }

            /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
            /// • 一个基于 port 的Source 的事件。
            /// • 一个 Timer 到时间了
            /// • RunLoop 自身的超时时间到了
            /// • 被其他什么调用者手动唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }

            /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

            /// 收到消息,处理消息。
            handle_msg:

            /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
            if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 

            /// 9.2 如果有dispatch到main_queue的block,执行block。
            else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 

            /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }

            /// 执行加入到Loop的block
            __CFRunLoopDoBlocks(runloop, currentMode);

            if (sourceHandledThisLoop && stopAfterHandle) {
                /// 进入loop时参数说处理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出传入参数标记的超时时间了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                /// 被外部调用者强制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer/observer一个都没有了
                retVal = kCFRunLoopRunFinished;
            }

            /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
        } while (retVal == 0);
    }

    /// 10. 通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

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

有一个这样的问题:当我们点击一个app,从我们点击到程序启动、程序运行再到程序杀死这个过程,系统都发生了什么呢?

实际上当我们调用了main函数之后,会调用UIApplicationMain函数,在这个函数内部会启动主线程的RunLoop,然后经过一系列的处理,最终主线程的RunLoop会处于一个休眠状态,然后我们此时如果点击一下屏幕,会转化成一个Source1来让我们的主线程唤醒,然后当我们杀死程序时,会调用RunLoop的退出,同时发送通知告诉观察者

找到一张总结图帮助记忆:

以上就是分析IOS RunLoop的事件循环机制的详细内容,更多关于IOS RunLoop的事件循环机制的资料请关注我们其它相关文章!

时间: 2021-06-04

iOS如何开发简单的手绘应用实例详解

开发一款简单的 iOS 手绘应用, 收集点,绘制形状,给形状着色,呈现给用户,好像就完了 框架是 Quartz2D 1, 收集点 首先需要有一个界面 UIView, 用这个界面监听用户的手势,收集点 用户按下手指 location(in, 从触摸事件中,获得在画板中的坐标 var lastPoint = CGPoint.zero override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard l

iOS WKWebview 白屏检测实现的示例

前言 自ios8推出wkwebview以来,极大改善了网页加载速度及内存泄漏问题,逐渐全面取代笨重的UIWebview.尽管高性能.高刷新的WKWebview在混合开发中大放异彩表现优异,但加载网页过程中出现异常白屏的现象却仍然屡见不鲜,且现有的api协议处理捕捉不到这种异常case,造成用户无用等待体验很差.     针对业务场景需求,实现加载白屏检测.考虑采用字节跳动团队提出的webview优化技术方案.在合适的加载时机对当前webview可视区域截图,并对此快照进行像素点遍历,如果非白屏颜

iOS实现折叠单元格

本文实例为大家分享了iOS实现折叠单元格的具体代码,供大家参考,具体内容如下 思路 点击按钮或cell单元格来进行展开收缩, 同时使用一个BOOL值记录单元格展开收缩状态.根据BOOL值对tableView的高度和button的image进行实时变更. 注意点: 在执行- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath( 点击当前单元格)方法时,收缩单元格,显示当前

iOS程序性能优化的技巧

1. 用ARC管理内存 ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露.它自动为你管理retain和release的过程,所以你就不必去手动干预了.忘掉代码段结尾的release简直像记得吃饭一样简单.而ARC会自动在底层为你做这些工作.除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存. 2.尽量把views设置为透明 如果你有透明的Views你

iOS实现音乐播放器图片旋转

本文实例为大家分享了iOS实现音乐播放器图片旋转的具体代码,供大家参考,具体内容如下 通过给继承与 UIImageView 的类 CXGImageView 添加 CABasicAnimation 转动动画,实现播放器图片转动效果. 主要提供三个方法: startRotating, stopRotating,resumeRotate startRotating /// 开始动画 func startRotating() { let rotateAnimation = CABasicAnimatio

iOS蓝牙设备名称缓存问题的解决方法

1. 问题背景 当设备已经在 App 中连接成功后 修改设备名称 App 扫描到的设备名称仍然是之前的名称 App 代码中获取名称的方式为(perpheral.name) 2. 问题分析 当 APP 为中心连接其他的蓝牙设备时. 首次连接成功过后,iOS系统内会将该外设缓存记录下来. 下次重新搜索时,搜索到的蓝牙设备时,直接打印 (peripheral.name),得到的是之前缓存中的蓝牙名称. 如果此期间蓝牙设备更新了名称,(peripheral.name)这个参数并不会改变,所以需要换一种方

IOS开发之多线程NSThiread GCD NSOperation Runloop

IOS中的进程和线程 通长来说一个app就是一个进程 ios开发中较少的运用进程间的通信(XPC),绝大多数使用线程. 在ios开发中,为了保证流畅性以及线程安全,所有与UI相关的操作都应该放在主线程,所以有时候主线程也叫UI线程. 影响UI体验,耗时时间较长的操作,尽量放到非主线程中.比如网络请求以及和本地的IO操作. 在IOS开发中有关于多线程的知识点主要包括:NSThread.GCD.NSOperation和Runloop NSThread NSthread就是一个线程,它的底层是对pth

iOS 如何高效的使用多线程

一.多线程简述 线程是程序执行流的最小单元,一个线程包括:独有ID,程序计数器 (Program Counter),寄存器集合,堆栈.同一进程可以有多个线程,它们共享进程的全局变量和堆数据. 这里的 PC (Program Counter) 指向的是当前的指令地址,通过 PC 的更新来运行我们的程序,一个线程同一时刻只能执行一条指令.当然我们知道线程和进程都是虚拟的概念,实际上 PC 是 CPU 核心中的寄存器,它是实际存在的,所以也可以说一个 CPU 核心同一时刻只能执行一个线程. 不管是多处

iOS实现电子签名

本文实例为大家分享了iOS实现电子签名的具体代码,供大家参考,具体内容如下 实现原理 1.使用拖动手势记录获取用户签名路径. 2.当用户初次接触屏幕,生成一个新的UIBezierPath,并加入数组中.设置接触点为起点.在手指拖动过程中为UIBezierPath添加线条,并重新绘制,生成连续的线. 3.手指滑动中不断的重新绘制,形成签名效果. 4.签名完成,转化为UIImage保存. class CXGSignView: UIView { var path: UIBezierPath? var

iOS中各种UI控件属性设置示例代码

//视图已经加载完了,可以进行ui的添加了 - (void)viewDidLoad { [superviewDidLoad]; // Do any additional setup after loading the view. //初始化UILabel注意指定该对象的位置及大小 UILabel *lb = [[UILabelalloc]initWithFrame:CGRectMake(0,20,300,200)]; //设置文字 lb.text =@"label测试我在学习中学些ui stor

Android开发中自定义ProgressBar控件的方法示例

本文实例讲述了Android开发中自定义ProgressBar控件的方法.分享给大家供大家参考,具体如下: 很简单,首先加载Drawable,在onMeasure设置好其区域大小, 然后使用canvas.clipRect绘图 public class ProgressView extends ImageView { private Drawable maskDraw; /** * 加载的进度 0-100 */ private int mProcess = 20; public ProgressV

C#向Word文档中添加内容控件的方法示例

前言 大家应该都知道在MS Word中,我们可以通过内容控件来向word文档中插入预先定义好的模块,指定模块的内容格式(如图片.日期.列表或格式化的文本等),从而创建一个结构化的word文档. 下面就来看看如何使用C#给word文档添加组合框.文本.图片.日期选取器及下拉列表等内容控件(这里我借助了一个word组件Spire.Doc). 添加组合框内容控件 组合框用于显示用户可以选择的项目列表.和下拉列表不同的是组合框允许用户编辑或添加项. 核心代码如下: //给段落添加一个内容控件并指定它的S

Android官方的侧滑控件DrawerLayout的示例代码

导语 侧滑控件,以前大家用的可能是三方的SlidingMenu控件,最近在看谷歌源码项目,意外的看到一个 DrawerLayout 控件.上网一查,原来这个控件是官方给我们提供的一个侧滑菜单控件.既然谷歌已经提供了一个侧滑控件,我们又何必去用一个三方的SlidingMenu控件来实现相同的效果.于是,我决定自己手敲一个Demo来实现看看. 1.DrawerLayout效果图 2.DrawerLayout 的介绍 DrawerLayout的官方文档介绍链接:http://androiddoc.qi

Android更多条目收缩展开控件ExpandView的示例代码

在Android开发中,我们经常使用列表控件,而有时候列表控件条目中又会是多条目数据,这时候,我们无法确定每个条目的数据多少,而为了美观,我们就希望条目统一高度,多数据的条目能够进行折叠.展开.今天,就为大家介绍一个这样的自定义控件 ExpandView. 效果演示图 演示图 Android Studio集成方式 dependencies{ compile 'com.wkp:ExpandView:1.0.4' //Android Studio3.0+可用以下方式 //implementation

WPF实现控件拖动的示例代码

实现控件拖动的基本原理是对鼠标位置的捕获,同时根据鼠标按键的按下.释放确定控件移动的幅度和时机. 简单示例: 在Grid中有一个Button,通过鼠标事件改编Button的Margin属性,从而改变Button在Grid中的相对位置. <Grid Name="gd"> <Button Width=90 Height=30 Name="btn">button</Button> </Grid> 为Button控件绑定三个事

iOS中使用UISearchBar控件限制输入字数的实现方法

废话不多说了,直接给大家上关键代码了,具体代码如下所述: - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText // called when text changes (including clear) { if (searchBar.text.length > IMPUT_MAX){ searchBar.text = [searchBar.text substringToIndex:15]

JS获取页面input控件中所有text控件并追加样式属性

如题,将使用jquery来在页面加载时获取页面input控件中所有text控件并添加样式. 以前写过的页面上所使用的text控件width属性太小需要改动,改动牵扯页面上所有text的属性,改动每个控件的width感觉会很麻烦,所以就想通过在页面加载的时候将要改动的text找到并添加属性. 首先先写了一个css样式: 复制代码 代码如下: .wid { width:205px; } 在页面加载中所写的代码如下: $("input[type=text]").addClass("

Android开发实现AlertDialog中View的控件设置监听功能分析

本文实例讲述了Android开发实现AlertDialog中View的控件设置监听功能.分享给大家供大家参考,具体如下: 之前给弹出的AlertDialog中的控件设置监听时,老是报空指针异常,之所以报空指针异常,是因为我findViewById写的有问题,因为我们需要给弹出框中的控件设置监听,直接用findViewById是找不到弹出框中的控件的,需要利用Dialog.findViewById或者利用你找到的弹出框中的View,然后view.findViewById;具体看下面代码 packa

设置jquery UI 控件的大小方法

如下所示: #ui-datepicker-div { font-size: 12px; } 以上这篇设置jquery UI 控件的大小方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.