热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RunLoop原理

RunLoop原理-【原英】https:developpaper.comios-在知道Runloop之前一般来说,一个线程只能执行一个任务,执行完就会退出。当我们需要让线程可

【原英】https://developpaper.com/ios-...

在知道Runloop之前

一般来说,一个线程只能执行一个任务,执行完就会退出。当我们需要让线程可以随时处理事件而不退出线程,我们就要使用Runloop
它内部是一个do while循环,不断地处理各种任务,保证线程能够的连续运行。

Runloop的目的

保证线程中有任务时可以执行线程工作。当线程中没有任务时,让线程休眠,提高程序性,节省资源。

Runloop的作用

简述详述
保持程序连续运行应用程序启动后,将启动主线程。当主线程启动时,会启动主线程对应该的runloopRunloop可以保证线程不会被破坏。如果主线程没有被销毁,程序将继续运行。
处理应用程序中的各种事件事件响应、手势识别、界面刷新、自动释放池、NSTimer等事件处理。
节省CPU资源,提高程序性当线程中有任务时,确保线程能够工作。当线程没有任务时,让线程休眠,提高程序性能,节省资源。该做事的时候做事,该休息的时候休息。

Runloop原理

名称类型所在框架原子性
NSRunloopNSObjectFundation线程不安全
CFRunloopstructCoreFundation线程安全

为了更好地理解Runloop,阅读源代码是一个不错的选择。老司机说有了源码,runloop就不会那么神秘了。首先,我们通常说runloop有两种。一个是NSRunloop,另一个是CFRunloop。那么让我先通过源码了解一下CFRunloop。首先,我们看一下基本的数据结构

1.CFRunloop的定义。

这里我注释了所有需要注意的参数

struct __CFRunLoop {
    CFRuntimeBase           _base;
    pthread_mutex_t         _lock;      /* locked for accessing mode list */
    __CFPort                _wakeUpPort;    // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop
    Boolean                 _unused;
    volatile                _per_run_data *_perRunData; // reset for runs of the run loop
    pthread_t               _pthread;         //RunLoop对应的线程
    uint32_t                _winthread;
    CFMutableSetRef         _commonModes;     //存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef         _commonModeItems; //存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef        _currentMode;     //当前运行的mode
    CFMutableSetRef         _modes;           //存储的是CFRunLoopModeRef
    struct _block_item     *_blocks_head;     //doblocks的时候用到
    struct _block_item     *_blocks_tail;
    CFTypeRef             _counterpart;
};

可以看出CFRunloop是一个有很多属性的结构体。
看一下这个结构中我们需要注意的参数。每个runloop都有自己的mode,并且有不止一种mode,mode存放着runloop要处理的事件源。事件源有3种,sourcetimerobserver
Runloop有很多模式,但是在某个时间只能有一种特定的模式,即_currentMode。下面第二项描述的是runloop的模式,它与runloop结构体(mode)相关参数中的几个模式有关:

名称模式
_currentMode当前的runloop模式
_modes当前运行中的所有模式

另外,runloop 中的一种模式是 NSRunloop 常用模式。这种模式没有意义。它只是标记模式的集合;

名称模式
_commonModes指在NSRunloopCommonmodes 模式下保存的模式。我们还可以在这个集合中添加自定义模式
_commonModeItems表示添加到NSRunloopCommonmodes的源/定时器;

2.CFRunloop的源代码。

struct __CFRunLoopMode {
    CFRuntimeBase           _base;
    pthread_mutex_t         _lock;    /* must have the run loop locked before locking this */
    CFStringRef             _name;   //mode名称
    Boolean                 _stopped;    //mode是否被终止
    char                    _padding[3];
    // ------------->
    //几种事件
    CFMutableSetRef         _sources0;  //sources0
    CFMutableSetRef         _sources1;  //sources1
    CFMutableArrayRef       _observers; //通知
    CFMutableArrayRef       _timers;    //定时器
    CFMutableDictionaryRef  _portToV1SourceMap; //字典  key是mach_port_t,value是CFRunLoopSourceRef
    // <-------------
    __CFPortSet             _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    CFIndex                 _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

其实CFRunloop相关的几个定义都是结构体,CFRunloopMode也是结构体。看代码了解CFRunloopMode的几个相关参数,主要是上面标注的四个参数

如上所述,runloop 用于处理事件。它主要处理三种类型的事件:源source定时器timer观察者observer。那么source也可以分为source0source1两种。CFRunloopMode的定义中有四个集合,分别代表存储这四个事件源的集合,如上所述。

runloop 中的模式主要有以下几种:

名称描述
KCFRunloopDefaultModeapp的默认模式。通常,主线程以这种模式运行。
UitrackingRunloopMode界面跟踪模式,用于Scrollview跟踪触摸滑动,保证界面滑动不受其他模式影响。
KCFRunloopCommonMode这是一个占位符模式。它用作标记 KCFRunloopDefaultModeUITrackingRunnoopMode。这不是真正的模式。
UiinitializationRunloopModeapp刚启动时进入的第一个模式。启动后将不再使用。
CSeventreceiveRunloopMode接受系统事件的内部模式。通常不使用

runloop启动时,只能选择一种模式作为currentmode。如果需要切换模式,只能退出当前模式,重新选择一个模式进入。

这里基于上面对CFRunloopCRFunloopMode的理解,RunloopMode保存在runloop中,实际执行的任务保存在RunloopMode中。

3.RunloopMode

RunloopMode 存储了runloop要处理的事件源。事件源有3种:Southtimerobserver

名称描述
South事件源
timer定时器
observer监听者

1) CFRunloopSourceRefSouth 事件的来源。看看它的定义。

struct __CFRunLoopSource {
    CFRuntimeBase           _base;
    uint32_t                _bits; // 用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
    pthread_mutex_t         _lock;
    CFIndex                 _order; // immutable
    CFMutableBagRef         _runLoops;
    union {
        CFRunLoopSourceContext version0; // immutable, except invalidation
        CFRunLoopSourceContext1 version1; // immutable, except invalidation
    } _context;
};

CFRunloopSourceRef 是runloop要处理的事件源之一。Version0Version1source0source1 根据不同事件的处理来区分的。

2)CFRunLoopTimerRefrunloop的相关定时器事件和定时器,定时执行一个任务,也在runloop中处理。

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;  //标记fire状态
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;        //添加该timer的runloop
    CFMutableSetRef _rlModes;     //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;      //理想时间间隔    /* immutable */
    CFTimeInterval _tolerance;    //时间偏差      /* mutable */
    uint64_t _fireTSR;            /* TSR units */
    CFIndex _order;            /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context;    /* immutable, except invalidation */
};

3)CFRunLoopObserverRefCFRunloopObserverRef 是runloop的监听器,可以监听runloop的状态变化。

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;        /* immutable */
    CFIndex _order;            /* immutable */
    CFRunLoopObserverCallBack _callout;    /* immutable */
    CFRunLoopObserverContext _context;    /* immutable, except invalidation */
};

Runloop在运行时有以下状态:


【面试点】
您可以将观察结果添加到runloop中。通过监控runloop的状态来判断是否卡顿。创建observer观察者,将创建的observer加入到主线程runloop的普通模式中,并创建一个连续的子线程来监控主线程的runloop状态。一旦发现睡眠前的kCFRonloopBeforeSource状态或者唤醒后的kCFRonloopFafterWaiting状态,在设定的时间阈值内没有变化,就可以判断为卡住了,转储堆栈的信息,从而进一步分析哪个方法有执行时间长。
以上为runloop的基本数据结构分析。

4、Runloop是如何运行的?

首先,如何创建一个runloop?其实runloop不需要手动创建。任何 runloop 都与一个线程相关联。先有线程,后有runloop。Apple 提供了两个 API。让我们获取runloop、CFRunloopGetMain()CFRunloopGetCurrent()。这两个方法分别获取当前线程的mainrunlooprunloop

从上面两个函数可以看出,runloop是通过_CFRunloopGet0(),它以线程为参数,与通过key从NSDictionary中获取值非常相似。接下来看看_CRFunloopGet0()的实现。

获取线程的runloop。首先,以线程为key,从全局字典中查找。如果没有找到,就新建一个,以threadkeyrunloopvalue保存到全局字典中(如果全局字典不存在,先初始化全局字典,并创建MainRunloop保存到全局字典中)。以下是源代码,我添加了注释。

以上就是获取当前runloop的原理,以及任务在runloop内部是如何执行的。这是一个图表。

CFRunlooprunCFRunloopRuninMode 都在内部调用 CFRunloopRunspecificCFRunloopRunspecific内部调用__CFRunloopRunCFRunloopRunspecific__CFRunloopRun一起就是runloop的完整实现。看看下面的伪代码解释,就是runloop的内部逻辑:

Runloop和线程的关系

  1. Runloop 存储在一个全局字典中。线程作为key,runloop作为Value。
  2. 第一次创建线程时,没有runloop对象。Runloop 将在第一次获取时创建。
  3. Runloop 在线程结束时会被销毁。
  4. 主线程的runloop已经自动获取(创建),子线程默认不开启runloop。
  5. 每个线程都有一个唯一的runloop对象与之对应。
  6. 先有线程,后有runloop。

在日常开发中如何使用Runloop

1.控制线程生命周期(线程保持活动,线程永远保持)。原理:如果模式中没有soure0/source1/timer/observer,runloop会立即退出。因此,为了不让它退出,可以在runloop中添加一个soure1。这是af2中使用的原理。
下面AFNetworking的一个例子:

  1. UITableView 延迟加载图片。将SETIMAGE置于NSDefaultRunloopMode中,即滑动时不会调用分配图片的方法,但滑动后切换到NSDefaultRunloopMode才会调用。

[self.img performSelector:@selector(setImage:) withObject:image afterDelay:0inModes:[NSDefaultRunLoopMode]];

3.解决了滑动时NSTimer停止工作的问题(将timer添加到NSRunLoopCommonModes模式)。

NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

默认情况下,计时器处于 NSDefaultRunloop 模式。当我们滑动页面的时候,runloop会切换到UITracking runloopmode,所以我们的定时器会停止工作。就像商场里的倒计时一样,当我们滑动页面时,倒计时就会停止。为了解决这个问题,我们需要让定时器工作在UITracking runloopmode,和NSRunloopCommonModes,这个模式相当于NSDefaultRunloopModeUITrackingRunloopMode的组合。因此,为定时器指定NSRunnoopcommonModes模式,使定时器可以在NSDefaultRunloopModeUITrackingRunnloopMode两种模式下运行。

  1. 另外,可以通过监控runloop的状态来处理卡顿问题。

runloop的进入sleep前和唤醒后的两个loop状态定义的值分别是kCFRenloopbeforesourceskCFRenloopafterwaiting,即触发source0回调接收mach_port消息有两种状态。创建observer观察者,将创建的observer加入到主线程runloop的普通模式中,并创建一个连续的子线程来监控主线程的runloop状态。一旦发现睡眠前的kCFRonloopBeforeSource状态或者唤醒后的kCFRonloopFafterwaiting状态,在设定的时间阈值内没有变化,就可以判断为卡住了,转储堆栈的信息,从而进一步分析哪个方法有执行时间长。

以上是开发中常用的runloop相关应用。


推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了如何使用iptables添加非对称的NAT规则段,以实现内网穿透和端口转发的功能。通过查阅相关文章,得出了解决方案,即当匹配的端口在映射端口的区间内时,可以成功进行端口转发。详细的操作步骤和命令示例也在文章中给出。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
author-avatar
qzy4799723
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有