Runloop基本概念

概述:

1.概念
2.数据结构
3.事件循环机制
4.RunLoop和NSTimer的关系
5.RunLoop和多线程

1、什么是RunLoop?

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象

  • 没有消息需要处理时 休眠以避免资源占用;(用户态——>内核态)
  • 有消息需要处理时,立刻被唤醒。(内核态——>用户态)

<1.1>.用户态和内核态

  • 我们的应用程序一般都是运行在用户态层面上,用户态包含了开发所使用的绝大多数api。
  • 当我们发生了系统调用,使用到一些关于操作系统以及一些底层内核相关的指令或者api的话,就触发了系统调用。

<1.2>.为什么main函数不会退出?

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

1>.UIApplicationMain里面开启了一个主线程的Runloop,Runloop可以在有事情做的时候做事,没有事情做的时候等待。通过用户态和内核态的切换,从而避免资源的占用。
2>.Runloop可以接收消息和等待以及处理消息。
3>.等待不等于死循环。

2、Runloop的数据结构

NSRunLoop(Foundation框架)是CFRunLoop(Core Foundation框架)的封装,提供了面向对象的API。

CFRunLoop
CFRunLoopMode
Source/Timer/Observer

<2.1>.CFRunLoop

pthread:线程【一一对应(Runloop和线程)】
currentMode:CFRunLoopMode
modes:NSMutableSet<CFRunLoopMode *>
commonModes:NSMutableSet<NSString *>
commonModeItems:Observer、timer、source的集合

<2.2>.CFRunLoopMode

name:字符串,如NSDefaultRunloopMode
source0
source1
timers:NSMutableArray
observers:NSMutableArray

<2.3>.CFRunloopSource

source0:需要手动唤醒线程。
source1:具备唤醒线程的能力。

<2.4>.CFRunloopTimer

和NSTimer是免费调转换(toll-free bridge)的。

<2.5>.CFRunloopObserver

观测时间点:
1.kCFRunLoopEntry:入口时机,启动的时候
2.kCFRunLoopBeforeTimers:通知观察者将要对timer进行处理了
3.kCFRunLoopBeforeSources:将要处理source事件
4.kCFRunLoopBeforeWaiting:将要进行休眠状态
5.kCFRunLoopAfterWaiting:内核态切换到用户态不久
6.kCFRunLoopExit:退出的通知

<2.6>.Runloop和mode以及内部的source、timer、observer的关系:

相互关系.jpg

<2.7>. Modes说明:
一共有5个 Mode,常用的有仨,其中一个是占位用的。5个 Mode分别是:
<1>.DefaultMode:最常用的模式,适用于大部分情况,常用于处理 UI 事件;
<2>.UITrackingMode:用于在拖动时保持界面更新;
<3>.CommonModes:允许多个模式之间共享事件源和输入源,例如同时支持滚动和触摸事件;CommonMode不是实际存在的一种Mode。是同步Source/Timer/Observer到多个Mode中的一种种技术方案
<4>.UIInitializationMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用;
<5>.GSEventReceiveMode: 接受系统事件的内部 Mode,通常用不到。

<2.8>.CFRunloopCommonModes
CommonMode不是实际示存在的一种Mode。
是同步Source/Timer/Observer到多个Mode中的一种种技术方案。

3、事件循环的实现机制
实现逻辑.jpg

<3.1>.app从点击一个图标到启动、运行到最终退出,这个过程中系统都发生了什么?
调用main函数之后,内部会调用UIApplicationMain,启动主线程的runloop,主线程的runloop处于休眠状态,如果点击屏幕,会产生一个mach port,基于mach port会转成source1,把主线程唤醒,执行处理事件。当我们把程序杀死,就会发生一个runloop的退出,发送一个通知即将退出runloop。runloop退出之后,线程销毁掉了。

4、RunLoop和NSTimer的关系

void CFRunLoopAddTimer(runLoop, timer, commonMode)

5、RunLoop和多线程
  • 线程是和RunLoop-一对应的。
  • 主线程默认就有 Runloop。
  • 子线程的 Runloop 以懒加载的形式创建。
  • Runloop 存储在一个全局的可变字典里,线程是 key ,Runloop 是 value。

<5.1>.怎样实现一个常驻线程?

  • 1.为当前线程开启一个RunLoop。
  • 2.向该RunLoop中添加一个Port/ Source等维持RunLoop的事件循环。
  • 3.启动该RunLoop。
#import "RLObject.h"

static NSThread *thread = nil;
// 标记是否要继续事件循环
static BOOL runAlways = YES;

@implementation RLObject

+ (NSThread *)threadForDispatch {
    if (thread == nil) {
        @synchronized (self) {
            if (thread == nil) {
                // 创建线程
                thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
                // 启动
                [thread start];
            }
        }
    }
    return thread;
}

+ (void)runRequest {
    // 创建一个source
    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    
    // 创建RunLoop,同时向RunLoop的default模式下添加source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    // 如果可以运行
    while (runAlways) {
        @autoreleasepool {
            // 令当前的RunLoop运行在DefaultMode下面
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
        }
    }
    // 某一时机,静态变量 runAlways == NO的时候,保证跳出RunLoop,线程退出
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容