Runloop的内部结构与运行原理

什么是Runloop

Runloop顾名思义,就是运行循环。首先它根程序运行过程有关系,其次它是一种转圈圈的效果。但如果这么解释,恐怕谁都听不懂。

想要弄明白Runloop,就要搞清楚跟它有关联的一些概念,以及它自身的运作原理。

没有Runloop的程序

我们通过Xcode新建一个命令行项目,main.m文件里的代码如下

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
   
    @autoreleasepool {
   
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

运行程序,程序在执行完代码NSLog(@"Hello, World!");之后,就会通过 return 0;推出程序,这是一种线性的执行流程,从上到下,很容易理解。

当我们遇见Runloop

我们再新建一个iOS项目,你看到的main.m文件是这个样子的

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

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

我们很清楚,运行程序之后,我们会进入app的界面,然后app就不会退出了,会一直运行着。你有没有好奇过,同样都是main函数,为啥下面的这个能够不退出呢?对,这就是Runloop的功劳。

在命令行工程里面的main.m里面,是没有加Runloop的,而iOS工程的main.m里面,其实在UIApplicationMain()这个方法中,系统加上了Runloop,让程序可以一直循环运行下去不退出。

这么解释感觉还是有点僵硬,下面用伪代码的形式来描述一下UIApplicationMain()内部情况
说白了, Runloop其实就是一个do-while循环,每次循环一圈,都会判断一次retVal,决定是否结束循环,继续执行循环外的代码。

Runloop的作用简述
  • Runloopdo-while本质说明它就是为了保持程序的持续运行,不退出(试想一下手机上的app如果一打开就直接退出完事了,这个世界可能就没有手机什么事了)
  • 保持程序持续运行的根本目的,就是为了处理app的各种事件(触摸事件,定时器事件),因为这些事件并不是编写程序的时候就定好的,天知道用户什么时候会点击某个按钮,对吧。因此想我们一开始说的那种线性的程序运行方式,就无法处理这样的场景。当加上Runloop之后,在它的一次循环中,就可以去处理程序已经接收到的各种事件,在处理这些事件的过程中,程序还会不断的接受新来的事件,这样,下次循环就可以继续处理新来的事件。
  • 如果Runloop在某次循环之后,发现程序突然没有收集到更多事件供它去处理,它就会休眠,实际上就是系统让程序停在了Runloopdo-while循环里面的某一段代码上(注意程序此时是停住,而不是退出哦),如果过了一会程序为Runloop接受到了新来的事件,它的do-while循环就会被系统重新激活以继续运行。这种机制的好处是,当Runloop休眠的时候,CPU可以不用在它跟前侯着,转而把时间精力分配给别的事务,提高了CPU的使用效率。

你可把CPU想象成一个妈妈,Runloop就是刚出生的宝宝,宝宝醒的时候,妈妈就必须一直看着他,没功夫去干别的事情,宝宝睡眠的时候,妈妈就可以抓紧时间去做一些别的事情了。如果没有Runloop休眠机制,相当于这个宝宝永远不会睡觉,那这个妈妈不就凉凉了嘛~~

深入理解Runloop对象
iOS中Runloop的API
  • Foundation: NSRunLoop
  • Core Foundation: CFRunLoopRef

NSRunLoopCFRunLoopRef都代表Runloop对象,NSRunLoop是基于CFRunLoopRef的一层OC包装,CFRunLoopRef开源的

Runloop对象的获取
  • Foundation
    [NSRunloop currentRunLoop];获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop];获得主线程的Runloop对象

  • Core Foundation
    CFRunLoopGetCurrent();获得当前线程的RunLoop对象
    CFRunLoopGetMain();获得主线程的Runloop对象

Runloop与线程

为什么聊Runloop一定要搭上线程?我们知道,程序里的每一句代码,都会在线程(主线程/子线程)里面被执行,上面四种获得Runloop对象的代码也不例外,一定是跑在线程里面的。之前我们说到,Runloop是为了让程序不退出,其实更准确地说,是为了保持某个线程不结束,只要还有未结束的线程,那么整个程序就不会退出,因为线程是程序的运行和调度的基本单元。

线程Runloop的关系是一对一的,一个新创建的线程,是没有Runloop对象的,当我们在该线程里第一次通过上面的API获得Runloop时,Runloop对象才会被创建,并且通过一个全局字典将Runloop对象和该线程存储绑定在一起,形成一对一关系。

Runloop会在线程结束时销毁,主线程的Runloop已经自动获取过(创建),子线程默认没有开启RunLoop(直到你在该线程获取它)。RunLoop对象创建后,会被保存在一个全局的Dictionary里,线程作为keyRunloop对象作为value

关于Runloop的创建和保存,我们还可以在CF源码里面详细看看,Runloop的信息是写在CF源码文件夹CFRunLoop.c文件里面,我们可以在里面搜索到CFRunLoopGetCurrent()函数的实现

CFRunLoopRef CFRunLoopGetCurrent(void) {
   
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

CFRunLoopGetCurrent()中又是通过_CFRunLoopGet0来获得Runloop对象的Runloop的获取创建流程

Runloop对象底层结构

我们可以在源码CFRunloop.c中找到Runloop的定义


**************????__CFRunLoop????***********
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
   
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;// used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;  // reset for runs of the run loop
    uint32_t _winthread;

 //♥️♥️♥️♥️♥️♥️♥️核心组成♥️♥️♥️♥️♥️♥️
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
 //♥️♥️♥️♥️♥️♥️♥️核心组成♥️♥️♥️♥️♥️♥️

    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值