假期学习-- iOS runloop了解和使用

news2024/11/15 2:03:15

iOS runloop的总结和理解

https://juejin.cn/post/7167497134294433829?searchId=202408060922235E28560E3792F91107ED

image.png

runloop的概念和数据结构

一般情况下,程序(或者说线程)在执行完毕后会立即退出或销毁;,但如果对应线程上有runloop运行就会一直运行,时刻等待用户的输入操作;而且runloop可以在需要的时候跑起来,不需要的时候进入休眠等待唤醒,可以充分节省cpu资源;

runloop本质上感觉更像是一个事件循环,它保证了线程不被释放,在循环过程中接受各种事件源信息,处理信息之后交由线程执行 ;

在 iOS 应用程序中,事件的处理通常是通过 runloop 来实现的。具体来说,runloop 负责接收和处理各种事件源产生的事件,包括定时源和其他源。当事件到达时,runloop 会调用相应的回调函数,并将事件分发给合适的处理程序(线程)。

image.png

RunLoop机制官方图.png

runloop的基本作用

1.保证程序的持续运行,如果不添加runloop,线程在执行完添加的任务之后就会自动销毁,即使添加了强引用导致没被销毁,也无法添加任务了 ;

2.处理App中各种事件,比如触摸事件,定时器事件,这里简单介绍一下触摸事件进行处理的流程,可以结合后面要讲的看:

当用户触摸屏幕时,iOS的底层架构会将事件封装成IOHIDEvents对象,接着用mach port转发,Source1会接收这个对象,之后再回调__IOHIDEventSystemClientQueueCallback(),__IOHIDEventSystemClientQueueCallback()内触发Source0,Source0 再触发 _UIApplicationHandleEventQueue()。 (也就是说,runloop先接收定时源和事件源的消息,然后再执行回调将其加入线程中执行)

3.节省cpu资源提高性能 ;

关于RunLoop的5个类

CFRunLoopRef: 代表RunLoop的对象

CFRunLoopModeRef: 代表RunLoop的运行模式

CFRunLoopSourceRef: 就是RunLoop模型图中提到的输入源(事件源)

CFRunLoopTimerRef: 就是RunLoop模型图中提到的定时源

CFRunLoopObserverRef: 观察者, 能够监听RunLoop的状态改变.

  • 一个RunLoop对象中包含若干个运行模式.每一个运行模式下又包含若干个输入源、定时源、观察者.
  • 每次RunLoop启动时, 只能指定其中一个运行模式, 这个运行模式被称作当前运行模式CurrentMode.
  • 如果需要切换运行模式, 只能退出当前Loop, 再重新指定一个运行模式进入.
  • 这样做主要是为了分隔开不同组的输入源、定时源、观察者, 让其互不影响.

runloop的数据结构

结构还是一个嵌套的结构体:

大体如下:

image.png

一个Runloop对象(结构体),内有多个mode,mode中有多个事件源,定时源,观察者;

但要注意runloop只能运行一种mode ;这也意味着会有mode的切换

而且mode基本已经确定好为五个类型,一般直接用这五种mode,在其中添加事件源,定时源,观察者 ;

系统默认注册的5个Mode:

  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

CFRunLoopSourceRef

  • CFRunLoopSourceRef是事件源, 有两种分类方法.
  1. 按照官方文档来分类
    • Port-Based Sources (基于端口)
    • Custom Input Sources (自定义)
    • Cocoa Perform Selector Sources
  1. 按照函数调用栈来分类
    • Source0: 非基于Port
    • Source1: 基于Port, 通过内核和其他线程通信, 接收、分发系统事件

runloop的基本执行原理

  • 原本系统就有一个RunLoop在检测App内的事件, 当输入源有执行操作的时候, 系统的RunLoop会监听输入源的状态, 进而在系统内部做一些对应的操作. 处理完事件后, 会自动回到睡眠状态, 等待下一次被唤醒.

image.png

  • 在每次运行开启RunLoop的时候, 所在线程的RunLoop会自动处理之前未处理的事件, 并且通知相关的观察者.
  1. 通知观察者RunLoop已经启动
  2. 通知观察者即将要开始定时器
  3. 通知观察者任何即将启动的非基于端口的源Source0
  4. 启动任何准备好的非基于端口的源Source0
  5. 如果基于端口的源Source1准备好并处于等待状态, 立即启动, 并进入步骤9
  6. 通知观察者线程进入休眠状态
  7. 将线程置于休眠直到下面任一种事件发生:
    • 某一事件到达基于端口的源Source1
    • 定时器启动
    • RunLoop设置的时间已经超时
    • RunLoop被显示唤醒
  1. 通知观察者线程将被唤醒

  2. 处理未处理的事件

    • 如果用户定义的定时器启动, 处理定时器事件并重启RunLoop, 进入步骤2
    • 如果输入源启动, 传递相应的消息
    • 如果RunLoop被显示唤醒而且时间还没超时, 重启RunLoop. 进入步骤2
  1. 通知观察者RunLoop结束.

runloop在哪里开启

UIApplicationMain函数内启动了Runloop,程序不会马上退出,而是保持运行状态。因此每一个应用必须要有一个runloop, 我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop,那么RunLoop一定是在程序的入口main函数中开启。

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

从这个函数往下走:

// 用DefaultMode启动
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

我们发现RunLoop确实是do while通过判断result的值实现的。因此,我们可以把RunLoop看成一个死循环。如果没有RunLoop,UIApplicationMain函数执行完毕之后将直接返回,也就没有程序持续运行一说了。

runloop的对象

CoreFoundation CFRunLoopRef对象

Fundation框架 (基于CFRunLoopRef的封装) NSRunLoop对象

获取对象:

Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

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

runloop和线程间的关系

  1. runloop与线程一一对应 ;
  2. 线程和runloop的对应关系会存储在一个全局的dictionary中
  3. 主线程的runloop是自动创建的,子线程的runloop需要手动创建维护 ;
  4. runloop不需要直接创建,在第一次获取时就会在内部创建,并在线程结束时销毁 ;

获取runloop的部分源码

// 拿到当前Runloop 调用_CFRunLoopGet0
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

// 查看_CFRunLoopGet0方法内部
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
	// 根据传入的主线程获取主线程对应的RunLoop
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    // 从字典里面拿,将线程作为key从字典里获取一个loop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    
    // 如果loop为空,则创建一个新的loop,所以runloop会在第一次获取的时候创建
    if (!loop) {  
	CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
	loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	
	// 创建好之后,以线程为key runloop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runloop
	if (!loop) { 
	    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	    loop = newLoop;
	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
	CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

可以看到在获取当前线程的runloop时,会先自动创建主线程的runloop,并把主线程和它的runloop的映射关系存入字典中;然后判读当前线程在字典中的映射,如果找到了取出对应的runloop,没找到就创建新的runloop并把映射存入全局字典 ;当线程结束时,RunLoop会被销毁。

RunLoop的结构体

__CFRunLoop结构体如下:

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
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

主要看下面两个属性:

CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;

结合前面我们讲到的runloop结构,可以知道,

CFRunLoopModeRef _currentMode; 是当前运行的runloopMode

CFMutableSetRef _modes; 是一个集合,存储着runloop对象中保存的Mode

往下走,__CFRunLoopMode结构体源码如下:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    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 */
};

主要看下面的成员变量:

CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
//run

CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer,而RunLoop启动时只能选择其中一个Mode作为currentMode

source1 和 source0

**Source1 : 基于Port的线程间通信 **(内核,系统的事件)

Source0 : 触摸事件,PerformSelectors (用户触发的事件)

当我们调用PerformSelectors时会触发Source0事件:

其实,当我们触发了事件(触摸/锁屏/摇晃等)后,由IOKit.framework生成一个 IOHIDEvent事件,而IOKit是苹果的硬件驱动框架,由它进行底层接口的抽象封装与系统进行交互传递硬件感应的事件,并专门处理用户交互设备,由IOHIDServices和IOHIDDisplays两部分组成,其中IOHIDServices是专门处理用户交互的,它会将事件封装成IOHIDEvents对象,接着用mach port转发给需要的App进程,随后 Source1就会接收IOHIDEvent,之后再回调__IOHIDEventSystemClientQueueCallback(),__IOHIDEventSystemClientQueueCallback()内触发Source0,Source0 再触发 _UIApplicationHandleEventQueue()。所以触摸事件看到是在 Source0 内的。

Timers : 定时器,NSTimer

两种创建方式:

  NSTimer* timer = [NSTimer timerWithTimeInterval:4.0 target:self selector:@selector(run02) userInfo:nil repeats:NO] ;
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
    NSLog(@"NSTimer ---- timer调用了");
}];

两者间的区别在于,第二种方式创建的NStimer会自动添加到NSrunloopDefaultmode中;

Observer : 监听器,用于监听RunLoop的状态

RunLoop相关类及作用

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source、Timer、Observer 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode 如果需要切换Mode,只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

注意:一种Mode中可以有多个Source(事件源,输入源,基于端口事件源例键盘触摸等) Observer(观察者,观察当前RunLoop运行状态) 和Timer(定时器事件源)。但是必须至少有一个Source或者Timer,因为如果Mode为空,RunLoop运行到空模式不会进行空转,就会立刻退出。

RunLoop 有五种运行模式,其中常见的有1.2两种:

1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode 
Mode间的切换

我们平时在开发中一定遇到过,当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollView,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 加入到RunLoop中才可以运行
    // 1. 把定时器添加到RunLoop中,并且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
    // [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 当textFiled滑动的时候,timer失效,停止滑动时,timer恢复
    // 原因:当textFiled滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效,当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动了
    
    // 2. 当我们将timer添加到UITrackingRunLoopMode模式中,此时只有我们在滑动textField时timer才会运行
    // [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // 3. 那个如何让timer在两个模式下都可以运行呢?
    // 3.1 在两个模式下都添加timer 是可以的,但是timer添加了两次,并不是同一个timer
    // 3.2 使用站位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,下面两种模式被打上标签
    //0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}
    //2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}
    // 因此也就是说如果我们使用NSRunLoopCommonModes,timer可以在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    NSLog(@"%@",[NSRunLoop mainRunLoop]);
}
-(void)show
{
    NSLog(@"-------");
}

同样的道理还适用于ImageView的延迟显示 ;

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s",__func__);
    // performSelector默认是在default模式下运行,因此在滑动ScrollView时,图片不会加载
    // [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"abc"] afterDelay:2.0 ];
    // inModes: 传入Mode数组
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"abc"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
CFRunLoopSourceRef事件源(输入源)

Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)

Source1:基于Port的 通过内核和其他线程相互发送消息(与内核相关)

CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
     //创建监听者
     /*
     第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
     第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
     第三个参数 Boolean repeats:YES:持续监听 NO:不持续
     第四个参数 CFIndex order:优先级,一般填0即可
     第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
     */
     /*
     所有事件
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
     kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
     kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
     kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
     kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
     kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要处理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要处理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒来了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
                
            default:
                break;
        }
    });
    
    // 给RunLoop添加监听者
    /*
     第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
     第二个参数 CFRunLoopObserverRef observer 监听者
     第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     /*
     CF的内存管理(Core Foundation)
     凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
     GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
     */
    CFRelease(observer);
}

runloop的处理逻辑

之前讲过,这里主要展示相关的源码:

// 共外部调用的公开的CFRunLoopRun方法,其内部会调用CFRunLoopRunSpecific
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 经过精简的 CFRunLoopRunSpecific 函数代码,其内部会调用__CFRunLoopRun函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知Observers : 进入Loop
    // __CFRunLoopDoObservers内部会调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函数
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // 核心的Loop逻辑
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 通知Observers : 退出Loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

// 精简后的 __CFRunLoopRun函数,保留了主要代码
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知Observers:即将处理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        
        // 通知Observers:即将处理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 处理Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 如果有Sources1,就跳转到handle_msg标记处
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        // 通知Observers:即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // 进入休眠,等待其他消息唤醒
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        
        // 醒来
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知Observers:已经唤醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
        if (被Timer唤醒的) {
            // 处理Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if (被GCD唤醒的) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被Sources1唤醒的
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        
        // 执行Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 根据之前的执行结果,来决定怎么做,为retVal赋相应的值
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
        
    } while (0 == retVal);
    
    return retVal;
}

RunLoop处理逻辑

runloop退出

  1. 主线程销毁RunLoop退出
  2. Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出
  3. 我们在启动RunLoop的时候可以设置什么时候停止
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>

runloop的应用

这部分看参考博客就行了,感觉大部分都差不多 ;

NSTimer和GCD定时器的区别

GCD 的定时器是基于系统内核的实现,相比于 NSTimer 是基于 runloop 的实现,其响应更加及时和精确。GCD 的定时器是由内核来管理,因此能够更加准确地执行任务。

runloop的唤醒方式

按顺序:被timer唤醒,被GCD唤醒,被source1唤醒 ;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115635.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

体育馆智能可视化:提升场馆管理与观赛体验

利用图扑可视化技术&#xff0c;实时呈现体育馆内各项动态&#xff0c;优化场馆管理&#xff0c;提升观众的观赛体验。

基于人工智能的手写数字识别系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 手写数字识别是一种经典的计算机视觉任务&#xff0c;目标是让机器能够识别手写数字。通过人工智能技术&#xff0c;特别是卷积神经网…

JS_变量

二、JS的变量 JS中的变量具有如下特征 1 弱类型变量,可以统一声明成var 2 var声明的变量可以再次声明 3 变量可以使用不同的数据类型多次赋值 4 JS的语句可以以; 结尾,也可以不用;结尾 5 变量标识符严格区分大小写 6 标识符的命名规则参照JAVA 7 如果使用了 一个没有声明的变量…

JAVA基础:值传递和址传递

1 值传递和址传递 值传递 方法调用时&#xff0c;传递的实参是一个基本类型的数据 形参改变&#xff0c;实参不变 public static void doSum(int num1,int num2){} main(){doSum(10,20);int i 10 ;int j 20 ;doSum(i,j) ; } public static void t1(int num){num 20 ;Sys…

STM32CubeMX和HAL库-新建项目

目录 新建项目 选择开发板 MCU图形化配置界面总览 MCU配置 新建项目 新建项目包含选择MCU创建项目、选择开发板新建项目和交叉选择MCU新建项目三部分。 1. 选择MCU创建项目 单击主菜单项File→New Project&#xff0c;或Home视图上的ACCESS TO MCU SELECTOR 按钮&#xff0c…

Mac+Pycharm配置PyQt6教程

安装包 pip install PyQt6 PyQt6-tools #查看Qt版本 pip show PyQt6 pip show pyqt6-tools 配置扩展工具 QTD(界面设计) Program&#xff1a;/Users/wan/PycharmProjects/NewDemo/venv/lib/python3.11/site-packages/qt6_applications/Qt/bin/Designer.app Working directo…

dubbo 服务消费原理分析之服务目录

文章目录 前言一、RegistryDirectory1、DynamicDirectory2、RegistryProtocol.doCreateInvoker2、RegistryProtocol.subscribe3、ListenerRegistryWrapper.subscribe4、FailbackRegistry.subscribe5、ZookeeperRegistry.doSubscribe6、RegistryDirectory.notify7、RegistryDire…

无人叉车里程计模块专题

1.无人叉车里程计模块传感器要求 2.里程计功能需求 3.无人叉车里程计模块测试用例 4.无人叉车里程计算法方案 5.源码

合理应用词云图,快速提升你的幻灯展示效果!|科研插图·24-09-08

小罗碎碎念 本期主题&#xff1a;词云图 这一期推文介绍一个非常具有实用价值的图——词云图&#xff0c;正好最近在准备开题答辩的PPT&#xff0c;顺手写一期推文&#xff0c;和大家分享一下。 R语言和Python的代码都准备了一份&#xff0c;其中Python的版本就是我用自己开始…

复制PPT模板

文章目录 环境需求步骤参考 环境 Windows 11 家庭中文版WPS Office&#xff1a;2024夏季更新 (17827) 注&#xff1a;查看WPS Office版本的方法&#xff1a; 点击左上角的“WPS Office”图标&#xff0c;然后点击右上角的三条横线&#xff0c;在弹出菜单中&#xff0c;选择“…

仕考网:公务员面试流程介绍

通知进面信息——资格审查——面试签到——抽签候考 面试形式&#xff1a; 面试分为结构化和无领导小组两种形式 1.在结构化面试中&#xff0c;当轮到某位考生时&#xff0c;引导员将在候考室宣布其编号&#xff0c;随后考生跟随引导人员前往考场入口。考生在开始考试时需回…

Origin画图——怎么才能使折线图更好看

1.这是化的原始图&#xff0c;看着也不错&#xff0c;但是总觉得缺少什么东西 2.下面就来对其进行改造&#xff0c;首先改的是坐标轴和标签。刻度的显示&#xff0c;网格线取消&#xff0c;坐标轴上轴右轴的刻度取消&#xff0c;字体的大小与style改成Arial。 3.对图层底色…

oc打包:权限弹窗无法正常弹出

在遇到编写了权限无法弹出弹窗时,需要查看是不是调用时机不对,这里直接教万能改法。 将权限获取方法编写在applicationDidBecomeActive 进入前台的生命周期接口中,如下: if (@available(iOS 14, *)) {NSLog<

基于yolov5的水稻叶病害检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv5的水稻叶病害检测系统是一种利用深度学习技术实现的高效、准确的病害识别工具。该系统采用YOLOv5算法&#xff0c;该算法以其高效的检测速度和良好的识别准确性在目标检测领域广受好评。 该系统能够实时处理水稻叶片的图像数据&#xff0c;快速准确地…

SpringBoot登录退出|苍穹外卖登录退出分析

文章目录 概要整体流程注意事项一、拦截路径二、token三、注册防止用户重复提交 苍穹外卖登录退出分析注意解决JWT退出后依然有效的问题 概要 结合Spring Boot和Vue3实现安全的用户登录和退出功能&#xff0c;并使用拦截器、JWT和Redis缓存来提高系统的安全性和性能。 整体流…

NC 合并二叉树

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 已知两颗二叉…

【魔法 / NOI】

题目 思路 动态规划&#xff1a; 状态定义&#xff1a; f [ k ] [ i ] [ j ] 对应使用了不超过 k 次魔法&#xff0c;从 i 到 j 的路径集合 f[k][i][j] 对应使用了不超过k次魔法&#xff0c;从i到j的路径集合 f[k][i][j]对应使用了不超过k次魔法&#xff0c;从i到j的路径集合 状…

驱动(RK3588S)第八课时:平台设备总线

目录 目标一、平台设备总线的概念1、什么是平台设备总线2、平台设备总线 platform 的匹配3、设备树和平台设备总线的关系&#xff0c;以及匹配 二、平台设备总线的函数接口1、注册设备端的资源信息2、设备端提供的资源的信息3、注销申请的设备端的资源4、驱动端的函数&#xff…

如何做好网络安全

随着互联网技术的飞速发展&#xff0c;网站已成为企业对外展示、交流和服务的重要窗口。然而&#xff0c;随之而来的网站安全问题也日益凸显&#xff0c;给企业的业务发展和用户数据安全带来了巨大威胁。因此&#xff0c;高度重视网站安全已成为网络安全的首要任务。今天我们就…

【Effective Java】多构造器参数使用构建器 (快速上手)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;2.1 为什么引入构建器2.2 建造者模式2.2.1书中的例子2.2.2 例子加上有效性检查 三、问题描述四、解决方案&#xff1a;…