欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > iOS——runLoop

iOS——runLoop

2024/12/5 3:19:06 来源:https://blog.csdn.net/m0_73348697/article/details/141941178  浏览:    关键词:iOS——runLoop

什么是runloop

RunLoop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行相应的处理逻辑。线程执行了这个函数后,就会处于这个函数内部的循环中,直到循环结束,函数返回。

RunLoop是一种高级的循环机制,让程序持续运行,并处理程序中的各种事件,让线程在需要做事的时候忙起来,不需要的话就让线程休眠。

RunLoop和线程是绑定在一起的,每条线程都有唯一一个与之对应的RunLoop对象。
不能自己创建RunLoop对象,但是可以获取系统提供的RunLoop对象。
主线程的RunLoop对象是由系统自动创建好的,在应用程序启动的时候会自动完成启动,而子线程中的RunLoop对象需要我们手动获取并启动。

Runloop解决了什么问题?

比如使用UIScrollView时:默认情况下,主RunLoop始终在应用程序中运行;它处理来自系统的消息并将其传输到应用程序。例如用户单击屏幕时的事件。
应用程序中的所有计时器都在运行循环上执行。

它是如何工作的?

RunLoop是一个循环,它有几种操作模式,可以帮助我们了解何时运行特定任务。

RunLoop可以采用以下模式:

  • Default 模式:在这种模式下,RunLoop是自由的,可以处理各种常规事件,比如用户触摸、计时器、网络请求回调等。因为它适合处理各种任务,所以称为“默认模式”。例如,在这个模式下,可以安全地执行耗时的计算或其他需要占用大量资源的操作。

  • Tracking 模式:当用户在进行一些需要快速响应的操作时(如滚动视图),RunLoop会切换到Tracking模式。这种模式下,RunLoop会暂时停止处理其他不重要的事件,以确保用户的操作能够得到迅速响应,从而避免卡顿。例如,当用户拖动列表时,这种模式会保证滚动的流畅性,而不被其他后台任务打断。

  • Initialization 模式:Initialization模式仅在初始化阶段使用。比如在应用程序刚启动或者线程刚创建时,RunLoop会进入这个模式来执行初始化工作。一旦初始化完成,RunLoop会退出这个模式。一般情况下,开发者不需要直接处理这个模式。

  • EventReceive 模式:这是一个系统级别的内部模式,用于接收和处理系统事件。开发者通常不会直接与这个模式交互,它主要用于系统内部的事件管理,例如处理硬件中断或系统通知等。

  • Common 模式:Common模式是一个占位符,用于将多个模式组合在一起。在实际使用中,开发者可以将某些事件源(如输入源和定时器)加入到Common模式,这样这些事件源可以在多个模式下共享。例如,可以将定时器添加到Common模式,使其在Default和Tracking模式下都能被触发。这有助于简化事件管理,避免在不同模式下重复添加事件源。

在主RunLoop上,这些模式会自动切换;开发人员可以使用它们来执行耗时的任务,这样用户就不会注意到界面挂起。

runloop的获取

两种获取方式:

// Foundation
NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // 获得当前RunLoop对象
NSRunLoop *runloop = [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
// Core Foundation
CFRunLoopRef runloop = CFRunLoopGetCurrent(); // 获得当前RunLoop对象
CFRunLoopRef runloop = CFRunLoopGetMain(); // 获得主线程的RunLoop对象

runloop的三种启动方式

//该方法会让 RunLoop 一直运行,除非有特定条件让它停止。
- (void)run;
//该方法设置了超时时间,如果超过了这个时间就会停止。但是如果提前把所有事件都处理完毕也会提前退出。
- (void)runUntilDate:(NSDate *)limitDate;
//方法RunLoop会运行一次,超时时间到达或者第一个source被处理,则RunLoop就会退出。
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

runloop和线程

  • RunLoop和线程是绑定在一起的,每条线程都有唯一一个与之对应的RunLoop对象。
  • 不能自己创建RunLoop对象,但是可以获取系统提供的RunLoop对象。
  • 主线程的RunLoop对象是由系统自动创建好的,在应用程序启动的时候会自动完成启动,而子线程中的RunLoop对象需要我们手动获取并启动。
    runloop和线程的关系:
    在这里插入图片描述

RunLoop在线程中不断检测,通过input source和timer source接受事件,然后通知线程进行处理事件。

runloop的结构

struct __CFRunLoop {CFRuntimeBase _base;// 基本的运行时结构,包含对象的引用计数等信息_CFRecursiveMutex _lock;// 递归锁,用于保护模式列表的访问__CFPort _wakeUpPort;// 用于CFRunLoopWakeUp的端口volatile _per_run_data *_perRunData;// 每次运行RunLoop时重置的数据_CFThreadRef _pthread;// 线程引用uint32_t _winthread; // Windows线程引用CFMutableSetRef _commonModes;// 通用模式集合CFMutableSetRef _commonModeItems; // 通用模式的项目集合CFRunLoopModeRef _currentMode; // 当前运行的模式CFMutableSetRef _modes;// 所有模式的集合struct _block_item *_blocks_head;// 块项链表的头部struct _block_item *_blocks_tail;// 块项链表的尾部CFAbsoluteTime _runTime;// RunLoop的运行时间CFAbsoluteTime _sleepTime;// RunLoop的休眠时间CFTypeRef _counterpart;// 对应的类型引用_Atomic(uint8_t) _fromTSD;// 原子变量,用于线程本地存储数据Boolean _perCalloutARP;// 每次回调的自动参考计数标志CFLock_t _timerTSRLock;// 定时器TSR锁
};

一个RunLoop对象包含一个线程(_pthread),若干个mode(_modes),若干个commonMode(_commonModes)。 不管是mode还是commonMode其类型都是CFRunLoopMode,只是在处理上有所不同。

CFRunLoopModeRef

struct __CFRunLoopMode {CFRuntimeBase _base;//锁, 必须runloop加锁后才能加锁pthread_mutex_t _lock;    /* must have the run loop locked before locking this *///mode的名称CFStringRef _name;//mode是否停止Boolean _stopped;char _padding[3];//sources0事件CFMutableSetRef _sources0;//sources1事件CFMutableSetRef _sources1;//observers事件CFMutableArrayRef _observers;//timers事件CFMutableArrayRef _timers;//字典  key是mach_port_t,value是CFRunLoopSourceRefCFMutableDictionaryRef _portToV1SourceMap;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中__CFPortSet _portSet;CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS//GCD定时器dispatch_source_t _timerSource;//GCD队列dispatch_queue_t _queue;// 当定时器触发时设置为trueBoolean _timerFired; // set to true by the source when a timer has firedBoolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO//MK_TIMER的portmach_port_t _timerPort;Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWSDWORD _msgQMask;void (*_msgPump)(void);
#endifuint64_t _timerSoftDeadline; /* TSR */uint64_t _timerHardDeadline; /* TSR */
};

从CFRunLoopMode的源码不难看出,一个CFRunLoopMode对象包含以下内容:

  • 唯一的一个name: 每个CFRunLoopMode对象都有一个唯一的名字来标识它。
  • 若干个sources0事件: sources0是指不需要内核参与的事件源,通常是一些用户定义的事件。
  • 若干个sources1事件: sources1是指需要内核参与的事件源,比如基于端口的事件。
  • 若干个timer事件: 定时器事件,用于在特定时间点或周期性地触发。
  • 若干个observer事件: 观察者事件,用于监听RunLoop状态的变化,比如即将进入休眠、即将退出等。
  • 若干个port: 端口,用于线程间的通信。
    RunLoop总是在某种特定的CFRunLoopMode下运行,这个特定的模式就是_currentMode。每次运行RunLoop时,都必须指定一个模式,这个模式决定了RunLoop在本次运行中会处理哪些事件。

而从CFRunloopRef对应结构体的定义可以知道,一个RunLoop对象包含若干个模式。也就是说,一个RunLoop可以在不同的模式下运行,每种模式包含不同的事件和端口。当切换模式时,RunLoop必须退出当前模式,然后重新进入另一个模式,以保证不同模式中的事件源、定时器和观察者互不影响。

通过这种机制,RunLoop可以在不同的上下文中运行,处理不同类型的事件,提供灵活的事件处理能力。

CFRunLoopMode有五种,其中在iOS中公开暴露只有NSDefaultRunLoopModeNSRunLoopCommonModes

  • NSDefaultRunLoopMode:默认模式是用于大多数操作的模式。大多数时候使用此模式来启动RunLoop并配置输入源。
  • NSConnectionReplyMode:Cocoa将此模式与NSConnection对象结合使用以监测回应。几乎不需要自己使用此模式。
  • NSModalPanelRunLoopMode:Cocoa使用此模式来识别用于模式面板的事件。
  • NSEventTrackingRunLoopMode:Cocoa使用此模式来限制鼠标拖动loop和其他类型的用户界面跟踪loop期间的传入事件。通常用不到。
  • NSRunLoopCommonModes:是NSDefaultRunLoopModeNSEventTrackingRunLoopMode集合,在这种模式下RunLoop分别注册了NSDefaultRunLoopModeUITrackingRunLoopMode。当然也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到CFRunLoopCommonModes组合。

CFRunLoopSourceRef-事件源

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

CFRunLoopSource是输入源的抽象,分为source0和source1两个版本。

  • source0:是App内部事件,只包含一个函数指针回调,并不能主动触发事件,使用时,你需要先调用CFRunLoopSourceSignal(source),将这个source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。
  • source1:source1包含一个mach_port和一个函数回调指针。source1是基于port的,通过读取某个port上内核消息队列上的消息来决定执行的任务,然后再分发到sources0中处理的。source1只供系统使用,并不对开发者开放。

CFRunLoopTimerRef–Timer事件

CFRunLoopTimer是定时器。下面是CFRunLoopTimer的源码:

struct __CFRunLoopTimer {CFRuntimeBase _base;uint16_t _bits;pthread_mutex_t _lock;//timer对应的runloopCFRunLoopRef _runLoop;//timer对应的modeCFMutableSetRef _rlModes;//下一次触发的时间CFAbsoluteTime _nextFireDate;//定时的间隔CFTimeInterval _interval;        /* immutable *///定时器允许的误差CFTimeInterval _tolerance;          /* mutable */uint64_t _fireTSR;            /* TSR units *///优先级CFIndex _order;            /* immutable *///任务回调CFRunLoopTimerCallBack _callout;    /* immutable *///上下文CFRunLoopTimerContext _context;    /* immutable, except invalidation */
}; 

从上面的代码可以看出,timer是依赖于runloop的,而且有函数指针回调,那么便可以在设定的时间点抛出回调执行任务。同时苹果官方文档也有提到CFRunLoopTimer和NSTimer是toll-free bridged(指在 Core Foundation 框架和 Foundation 框架中某些类型可以直接互换使用。)的,这就意味着两者之间可以相互转换。

CFRunLoopObserverRef–观察者

CFRunLoopObserver是观察者,监测RunLoop的各种状态的变化。

struct __CFRunLoopObserver {CFRuntimeBase _base;pthread_mutex_t _lock;//对应的runLoop对象CFRunLoopRef _runLoop;// 当前的观察的runloop个数CFIndex _rlCount;//runloop的状态CFOptionFlags _activities;        /* immutable */CFIndex _order;            /* immutable *///回调CFRunLoopObserverCallBack _callout;    /* immutable *///上下文CFRunLoopObserverContext _context;    /* immutable, except invalidation */
};

RunLoop的source事件源来监测是否有需要执行的任务,而observer则是监测RunLoop本身的各种状态的变化,在合适的时机抛出回调,执行不同类型的任务。
runloop用于观察的状态:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0),//即将进入runloopkCFRunLoopBeforeTimers = (1UL << 1),//即将处理timer事件kCFRunLoopBeforeSources = (1UL << 2),//即将处理source事件kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠kCFRunLoopAfterWaiting = (1UL << 6),//即将唤醒kCFRunLoopExit = (1UL << 7),//runloop退出kCFRunLoopAllActivities = 0x0FFFFFFFU
};

在这里插入图片描述

runloop实现原理

runloop启动

RunLoop启动有两个方法可供调用,分别是CFRunLoopRunCFRunLoopRunInMode

//启动当前线程的RunLoop,并使其在默认模式下运行,直到RunLoop被停止或完成。
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,并允许设定超时时间和是否在处理完一个source事件后返回。
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */CHECK_FOR_FORK();return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

CFRunLoopRun:

  • 调用 CFRunLoopRunSpecific 函数,将当前线程的RunLoop(通过 CFRunLoopGetCurrent() 获取)运行在默认模式下( kCFRunLoopDefaultMode ),并设置一个极大的超时时间( 1.0e10 ),表示基本上不会超时,且不在处理完一个source事件后立即返回( false )。
  • 调用 CHECK_FOR_FORK() 检查是否有进程fork(这是一种保护措施,确保fork后RunLoop的状态是安全的)。
  • 如果 result 不等于 kCFRunLoopRunStoppedkCFRunLoopRunFinished,则继续运行。 kCFRunLoopRunStoppedkCFRunLoopRunFinished 是RunLoop运行结束的标志。

获取runloop

  • CFRunLoopGetCurrent:获取当前线程的runloop对象
  • CFRunLoopGetMain:获取主线程的runloop对象

CFRunLoopGetCurrent

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

该方法内部调用了_CFRunLoopGet0方法,传入的参数是当前线程pthread_self(),由此可见CFRunLoopGetCurrent函数必须要在线程内部调用才能获取到RunLoop对象。

CFRunLoopGetMain

CFRunLoopRef CFRunLoopGetMain(void) {CHECK_FOR_FORK();static CFRunLoopRef __main = NULL; // no retain neededif (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS neededreturn __main;
}

该方法内部调用了_CFRunLoopGet0方法,传入的参数是主线程pthread_main_thread_np(),由此可见CFRunLoopGetCurrent函数不管是在主线程中还是在子线程中都可以获取到主线程的RunLoop。

_CFRunLoopGet0


static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
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);//创建主线程的RunLoopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());//把主线程的RunLoop保存到dict中,key是线程,value是RunLoopCFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);//此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoopsif (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&__CFRunLoops)) {CFRelease(dict);}//释放主线程RunLoopCFRelease(mainLoop);__CFLock(&loopsLock);}// 根据线程从__CFRunLoops获取RunLoopCFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));__CFUnlock(&loopsLock);//如果在__CFRunLoops中没有找到if (!loop) {//创建一个新的RunLoopCFRunLoopRef newLoop = __CFRunLoopCreate(t);__CFLock(&loopsLock);loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if (!loop) {//把新创建的RunLoop存放到__CFRunLoops中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)) {//注册一个回调,当当前线程销毁时销毁对应的RunLoop_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS - 1), (void (*)(void *))__CFFinalizeRunLoop);}}return loop;
}
  • 检查传入线程是否为主线程,如果是,将其转换为主线程。
  • 加锁并初始化全局字典,如果字典尚未初始化,则创建并初始化它。
  • 从字典中获取当前线程的 CFRunLoop,如果未找到则创建新的并存入字典。
  • 为当前线程设置特定数据,以便在线程销毁时清理对应的 CFRunLoop。
  • 返回获取或创建的 CFRunLoop。

由上面这段代码可知:
RunLoop和线程是一一对应的,是以线程为key,RunLoop对象为value存放在一个全局字典中的。
主线程的RunLoop会在初始化全局化字典时创建。
子线程的RunLoop会在第一次获取时创建。
当线程销毁时,对应的RunLoop也会随之销毁。

CFRunLoopRunSpecific

CFRunLoop 的核心运行逻辑。CFRunLoopRunSpecific 函数根据指定的模式和超时时间运行
在这里插入图片描述

unLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{// 检查是否在 fork 之后的子进程中运行CHECK_FOR_FORK();// 如果 rl 正在被释放,返回 kCFRunLoopRunFinishedif (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;// 锁定 CFRunLoop 实例,确保线程安全__CFRunLoopLock(rl);// 根据 modeName 找到本次运行的 modeCFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);// 如果没有找到 mode 或者找到的 mode 中没有注册事件则退出,不进入循环if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {Boolean did = false;// 如果找到了 mode,则解锁该 modeif (currentMode)__CFRunLoopModeUnlock(currentMode);// 解锁 CFRunLoop 实例__CFRunLoopUnlock(rl);// 根据是否处理了事件返回相应的结果return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;}// 保存当前的运行数据volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);// 取上一次运行的 modeCFRunLoopModeRef previousMode = rl->_currentMode;// 设置当前模式为找到的 moderl->_currentMode = currentMode;// 初始化结果为 kCFRunLoopRunFinishedint32_t result = kCFRunLoopRunFinished;// 如果当前模式有 kCFRunLoopEntry 类型的观察者,通知这些观察者即将进入 RunLoopif (currentMode->_observerMask & kCFRunLoopEntry)__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);// 调用实际的 RunLoop 运行函数,返回运行结果result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);// 如果当前模式有 kCFRunLoopExit 类型的观察者,通知这些观察者即将退出 RunLoopif (currentMode->_observerMask & kCFRunLoopExit)__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);// 解锁当前模式__CFRunLoopModeUnlock(currentMode);// 恢复之前的运行数据__CFRunLoopPopPerRunData(rl, previousPerRun);// 恢复之前的模式rl->_currentMode = previousMode;// 解锁 CFRunLoop 实例__CFRunLoopUnlock(rl);// 返回运行结果return result;
}

传入四个参数:

  • rl:当前运行的RunLoop对象。
  • modeName:指定RunLoop对象的mode的名称。
  • seconds:RunLoop的超时时间
  • returnAfterSourceHandled:是否在处理完事件之后返回。

从上面的代码我们可以获取到如下几点信息:

  • RunLoop运行必须要指定一个mode,否则不会运行RunLoop。
  • 如果指定的mode没有注册时间任务,RunLoop不会运行。
  • 通知observer进入runloop,调用__CFRunLoopRun方法处理任务,通知observer退出runloop。

__CFRunLoopRun

// __CFRunLoopRun函数,启动RunLoop
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {// 获取系统启动后的CPU运行时间,用于控制超时时间uint64_t startTSR = mach_absolute_time();// 如果RunLoop或者mode是stop状态,则直接return,不进入循环if (__CFRunLoopIsStopped(rl)) {__CFRunLoopUnsetStopped(rl);return kCFRunLoopRunStopped;} else if (rlm->_stopped) {rlm->_stopped = false;return kCFRunLoopRunStopped;}// mach端口,在内核中,消息在端口之间传递。初始为0mach_port_name_t dispatchPort = MACH_PORT_NULL;// 判断是否为主线程Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));// 如果在主线程 && runloop是主线程的runloop && 该mode是commonMode,则给mach端口赋值为主线程收发消息的端口if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();#if USE_DISPATCH_SOURCE_FOR_TIMERSmach_port_name_t modeQueuePort = MACH_PORT_NULL;if (rlm->_queue) {// mode赋值为dispatch端口_dispatch_runloop_root_queue_perform_4CFmodeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);if (!modeQueuePort) {CRASH("Unable to get port for run loop mode queue (%d)", -1);}}
#endif// GCD管理的定时器,用于实现runloop超时机制dispatch_source_t timeout_timer = NULL;struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));// 立即超时if (seconds <= 0.0) { // instant timeoutseconds = 0.0;timeout_context->termTSR = 0ULL;}// seconds为超时时间,超时时执行__CFRunLoopTimeout函数else if (seconds <= TIMER_INTERVAL_LIMIT) {dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);dispatch_retain(timeout_timer);timeout_context->ds = timeout_timer;timeout_context->rl = (CFRunLoopRef)CFRetain(rl);timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of contextdispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);dispatch_resume(timeout_timer);}// 永不超时else { // infinite timeoutseconds = 9999999999.0;timeout_context->termTSR = UINT64_MAX;}// 标志位默认为trueBoolean didDispatchPortLastTime = true;// 记录最后runloop状态,用于returnint32_t retVal = 0;do {// 初始化一个存放内核消息的缓冲池uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINImach_msg_header_t *msg = NULL;mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWSHANDLE livePort = NULL;Boolean windowsMessageReceived = false;
#endif// 取所有需要监听的port__CFPortSet waitSet = rlm->_portSet;// 设置RunLoop为可以被唤醒状态__CFRunLoopUnsetIgnoreWakeUps(rl);// 2.通知observer,即将触发timer回调,处理timer事件if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);// 3.通知observer,即将触发Source0回调if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);// 执行加入当前runloop的block__CFRunLoopDoBlocks(rl, rlm);// 4.处理source0事件,有事件处理返回true,没有事件返回falseBoolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);if (sourceHandledThisLoop) {// 执行加入当前runloop的block__CFRunLoopDoBlocks(rl, rlm);}// 如果没有Sources0事件处理 并且 没有超时,poll为false// 如果有Sources0事件处理 或者 超时,poll都为trueBoolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);// 第一次do..while循环不会走该分支,因为didDispatchPortLastTime初始化是trueif (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI// 从缓冲区读取消息msg = (mach_msg_header_t *)msg_buffer;// 5.接收dispatchPort端口的消息,(接收source1事件)if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {// 如果接收到了消息的话,前往第9步开始处理msggoto handle_msg;}
#elif DEPLOYMENT_TARGET_WINDOWSif (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {goto handle_msg;}
#endif}didDispatchPortLastTime = false;// 6.通知观察者RunLoop即将进入休眠if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);// 设置RunLoop为休眠状态__CFRunLoopSetSleeping(rl);// do not do any user callouts after this point (after notifying of sleeping)// Must push the local-to-this-activation ports in on every loop// iteration, as this mode could be run re-entrantly and we don't// want these ports to get serviced.__CFPortSetInsert(dispatchPort, waitSet);__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS// 这里有个内循环,用于接收等待端口的消息// 进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loopdo {if (kCFUseCollectableAllocator) {objc_clear_stack(0);memset(msg_buffer, 0, sizeof(msg_buffer));}msg = (mach_msg_header_t *)msg_buffer;// 7.接收waitSet端口的消息__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);// 收到消息之后,livePort的值为msg->msgh_local_port,if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));if (rlm->_timerFired) {// Leave livePort as the queue port, and service timers belowrlm->_timerFired = false;break;} else {if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);}} else {// Go ahead and leave the inner loop.break;}} while (1);
#else// 7.接收waitSet端口的消息__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS// 7.接收waitSet端口的消息__CFRunLoopWaitForMultipleObjects(rl, rlm->_ports, (poll ? 0 : INFINITE), &livePort, &windowsMessageReceived);
#endif__CFRunLoopLock(rl);__CFRunLoopModeLock(rlm);__CFRunLoopUnsetSleeping(rl);// 8.通知观察者RunLoop被唤醒if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);__CFPortSetRemove(dispatchPort, waitSet);if (MACH_PORT_NULL != livePort) {// 通知port已被使用didDispatchPortLastTime = (livePort == dispatchPort);#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINIhandle_msg:;// 9.处理消息,触发Source1回调CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(msg);__CFRunLoopDoSource1(rl, rlm, rls, msg, livePort);if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#elif DEPLOYMENT_TARGET_WINDOWSif (windowsMessageReceived) {// 9.处理消息__CFRunLoopProcessWindowsMessage(rl, rlm);} else {__CFRunLoopDoSource1(rl, rlm, rls, msg, livePort);}
#endif}__CFRunLoopDoBlocks(rl, rlm);// 返回条件:如果存在以下条件,返回相应的状态值// 1. runloop是否被显式唤醒退出// 2. 是否执行到超时// 3. 是否有source0事件需要处理// 4. 是否有source1事件需要处理// 5. 是否被显式唤醒退出// 6. 是否被模式退出// 7. 是否被timer退出if (__CFRunLoopIsStopped(rl)) {__CFRunLoopUnsetStopped(rl);retVal = kCFRunLoopRunStopped;} else if (rlm->_stopped) {rlm->_stopped = false;retVal = kCFRunLoopRunStopped;} else if (timeout_context->termTSR < mach_absolute_time()) {retVal = kCFRunLoopRunTimedOut;} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {retVal = kCFRunLoopRunFinished;} else if (sourceHandledThisLoop && stopAfterHandle) {retVal = kCFRunLoopRunHandledSource;}} while (0 == retVal);if (timeout_timer) {dispatch_source_cancel(timeout_timer);dispatch_release(timeout_timer);}free(timeout_context);return retVal;
}

该方法内部就是一个 do-while 循环,当调用该方法时,线程就会一直留在这个循环里面,直到超时或者手动被停止,该方法才会返回。在这里循环里面,线程在空闲的时候处于休眠状态,在有事件需要处理的时候,处理事件。该方法是整个RunLoop运行的核心方法。

  1. 通知观察者RunLoop已经启动:当RunLoop启动时,通知所有的观察者,RunLoop已经开始运行。可以在此时进行一些初始化操作。
  2. 通知观察者定时器即将触发:在RunLoop即将处理定时器事件时,通知观察者。观察者可以在此时进行一些操作,如更新UI或记录日志。
  3. 通知观察者任何不基于端口的输入源都将触发:在RunLoop即将处理非基于端口的输入源(Source0)事件时,通知观察者。这些输入源通常是应用程序内部事件。
  4. 触发所有准备触发的非基于端口的输入源:处理所有已经准备好的Source0事件,执行相应的回调函数。
  5. 如果基于端口的输入源已准备好并等待启动,立即处理事件;并进入步骤9:检查是否有基于端口的输入源(Source1)已经有事件需要处理。如果有,则处理这些事件,并跳到步骤9继续处理其他事件。
  6. 通知观察者线程进入休眠状态:在RunLoop即将进入休眠状态之前,通知所有的观察者,让他们知道RunLoop即将进入等待状态。可以在此时进行一些准备工作,如记录日志。
  7. 使线程进入睡眠状态,直到发生以下事件之一
    • 某一事件到达基于端口的源:如果有基于端口的事件到达(如文件描述符可读,网络套接字有数据等),唤醒RunLoop。
    • 定时器触发:如果定时器到期,唤醒RunLoop。
    • RunLoop设置的时间已经超时:如果等待超时,唤醒RunLoop。
    • RunLoop被唤醒:如果RunLoop被显式唤醒(如调用CFRunLoopWakeUp),唤醒RunLoop。
  8. 通知观察者线程即将被唤醒:在RunLoop从睡眠状态被唤醒之前,通知所有的观察者。观察者可以在此时进行一些操作,如更新UI或记录日志。
  9. 处理未处理的事件:唤醒后,处理所有未处理的事件:
    • 如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2:如果定时器到期,处理定时器事件并重新进入RunLoop循环。
    • 如果输入源启动,传递相应的消息:处理输入源的事件,传递相应的消息。
    • 如果RunLoop被唤醒而且时间还没超时,重启RunLoop。进入步骤2:如果RunLoop被显式唤醒且时间未超时,重新进入RunLoop循环。
  10. 通知观察者RunLoop结束:RunLoop即将结束时,通知所有的观察者。观察者可以在此时进行一些收尾工作,如释放资源或记录日志。

在这里插入图片描述

非基于端口的输入源(Source0):这些源通常不依赖于内核端口机制,直接调用某个函数来处理事件。例如,手动添加的事件处理函数。
基于端口的输入源(Source1):这些源依赖于内核端口机制,监听来自内核的消息。例如,文件描述符、网络套接字、Mach端口等。

__CFRunLoopServiceMachPort

在__CFRunLoopRun内部有一个内置的循环,这个循环会让线程进入休眠状态,直到收到新消息才跳出该循环,继续执行RunLoop。这些消息是基于mach port来进行进程之间的通讯的。

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {Boolean originalBuffer = true; // 标记缓冲区是否是原始缓冲区kern_return_t ret = KERN_SUCCESS; // 初始化返回值for (;;) { // 无限循环,直到接收到消息或发生错误mach_msg_header_t *msg = (mach_msg_header_t *)*buffer; // 将缓冲区指针转换为消息头指针msg->msgh_bits = 0; // 重置消息头的标志位msg->msgh_local_port = port; // 设置本地端口为指定的端口msg->msgh_remote_port = MACH_PORT_NULL; // 设置远程端口为空msg->msgh_size = buffer_size; // 设置消息大小为缓冲区大小msg->msgh_id = 0; // 重置消息ID// 根据是否无限超时,调用相应的休眠或轮询函数if (TIMEOUT_INFINITY == timeout) {CFRUNLOOP_SLEEP(); // 如果是无限超时,调用休眠函数} else {CFRUNLOOP_POLL(); // 如果有超时时间,调用轮询函数}// 调用mach_msg函数接收消息ret = mach_msg(msg,MACH_RCV_MSG | MACH_RCV_LARGE | ((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),0, // 不发送消息msg->msgh_size, // 接收消息的大小port, // 使用的端口timeout, // 超时时间MACH_PORT_NULL); // 没有通知端口CFRUNLOOP_WAKEUP(ret); // 唤醒RunLoop// 如果接收消息成功if (MACH_MSG_SUCCESS == ret) {*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL; // 设置livePort为本地端口return true; // 返回true表示成功}// 如果超时未接收到消息if (MACH_RCV_TIMED_OUT == ret) {if (!originalBuffer) free(msg); // 如果不是原始缓冲区,则释放内存*buffer = NULL; // 将缓冲区指针设为NULL*livePort = MACH_PORT_NULL; // 将livePort设为NULLreturn false; // 返回false表示失败}// 如果缓冲区太小,接收到过大的消息if (MACH_RCV_TOO_LARGE != ret) break; // 如果不是缓冲区太小的错误,跳出循环// 重新分配更大的缓冲区buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE); // 计算新的缓冲区大小if (originalBuffer) *buffer = NULL; // 如果是原始缓冲区,将其设为NULLoriginalBuffer = false; // 标记缓冲区不再是原始缓冲区*buffer = realloc(*buffer, buffer_size); // 重新分配内存}HALT; // 发生严重错误,终止程序return false; // 返回false表示失败
}

休眠:
当没有指定超时时间(即 timeout 为 TIMEOUT_INFINITY)时,线程进入休眠状态。
线程在休眠状态下不会主动消耗 CPU 资源,直到有事件发生唤醒线程。
适用于需要长时间等待事件的场景。

轮询:
当指定了超时时间时,线程不会一直挂起,而是会定期进行检查。
线程在轮询期间会消耗一定的 CPU 资源,但可以更及时地响应事件。
适用于需要在有限时间内频繁检查事件的场景。

该方法接收指定内核端口的消息,并将消息缓存在缓存区,供外界获取。该方法的核心是mach_msg方法,该方法实现消息的发送和接收。RunLoop调用这个函数去接收消息,如果没有接收到port的消息,内核会将线程置于等待状态。

RunLoop事件处理

处理事件主要涉及到如下几个方法:

  • __CFRunLoopDoObservers:处理通知事件。
  • __CFRunLoopDoBlocks:处理block事件。
  • __CFRunLoopDoSources0:处理source0事件。
  • __CFRunLoopDoSource1:处理source1事件。
  • __CFRunLoopDoTimers:处理定时器。
  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE:GCD主队列

上述方法回调上层的方法对应如下图所示:
在这里插入图片描述

小结

RunLoop的运行必定要指定一种mode,并且该mode必须注册任务事件。
RunLoop是在默认mode下运行的,当然也可以指定一种mode运行,但是只能在一种mode下运行。
RunLoop内部实际上是维护了一个do-while循环,线程就会一直留在这个循环里面,直到超时或者手动被停止。
RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态,否则线程处理事件。

RunLoop应用

NSTimer

我们创建timer时,之所以timer能运行,是因为创建timer时,一般情况下,是在主线程中创建,这时会默认将timer以defaultRunloopModel的类型加入主线程,而主线程的runloop对象默认是打开的,从而timer可以运行。
系统会将NSTimer自动加入NSDefaultRunLoopMode模式中,所以以下两段代码含义相同:

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

NSTimer在滑动时停止工作的问题

当我们不做任何操作的时候,RunLoop处于NSDefaultRunLoopMode
当我们进行拖拽时,RunLoop就结束NSDefaultRunLoopMode,切换到了UITrackingRunLoopMode模式下,这个模式下没有添加该NSTimer以及其事件,所以我们的NSTimer就不工作了
当我们松开鼠标时候,RunLoop就结束UITrackingRunLoopMode模式,又切换回NSDefaultRunLoopMode模式,所以NSTimer就又开始正常工作了。

如何解决NSTimer在滑动时停止工作

根据前面学的,NSRunLoopCommonModesNSDefaultRunLoopModeNSEventTrackingRunLoopMode的集合,因此如果想要让timer时刻都不会停止工作,可以使用NSRunLoopCommonModes,即直接让NSTimer在两种mode下都能工作。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

或者:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

NSTimer不准确的问题

  • 在RunLoop循环过程中,被NSTimer触发事件阻塞了,导致循环不能及时进行下去,延误之后NSTimer触发时间。
  • 在RunLoop循环过程中,在某一时刻主线程发生了阻塞情况,导致循环不能及时进行下去,厌恶NSTimer触发时间。
  • 在RunLoop循环过程中,发生了模式的转换,(比如UIScrollView的滑动) 导致原有模式下的NSTimer不会正常触发。(虽然可以指定NSTimer所处模式为NSRunLoopCommonModes,但是这种解决方法并不能改变run loops在特定模式下不能处理其余模式事件的本质。)
    以上情况都是由于NSTimer所依赖的run loops会被多种原因干扰正常循环,所以要想解决NSTimer精度问题,就要避免所依赖的run loops被外界干扰

解决办法:

  • 尽量避免将NSTimer放入容易受到影响的主线程runloops中。
  • 尽量避免将耗时操作放入NSTimer依赖的线程中。
  • 尽量避免在NSTimer触发事件中进行耗时操作,如果不能避免,将耗时操作移至其余线程进行。

GCD计时器

不过GCD定时器不同,GCD的线程管理是通过系统来直接管理的。GCD Timer是通过dispatch port给 RunLoop发送消息,来使RunLoop执行相应的block,如果所在线程没有RunLoop,那么GCD 会临时创建一个线程去执行block,执行完之后再销毁掉,因此GCD的Timer是不依赖RunLoop的。

RunLoop 响应用户操作的大致流程:

  1. 当用户进行某个操作(例如点击按钮)时,操作系统会将该事件发送给应用程序。
  2. 应用程序主线程会收到这个事件,并将其添加当前线程的 RunLoop 中。
  3. RunLoop 开始运行,并进入一个循环状态,不断地检查是否事件需要处理。
  4. 如果有事件需要处理,RunLoop 会将事件从队列中取出,并将其分发给相应的处理器(例如事件响应方法)。
  5. 处理器执行相应的代码处理事件,可能包括更新用户界面、执行业务逻辑等操作。
  6. 处理完事件后,RunLoop 继续循环,等下一个事件的到来。
  7. RunLoop 的关键是在循环中不断检查事件,并及时分发给处理器这样可以保证用户操作的响应速度,并且免阻塞主线程。

AutoReleasePool与RunLoop

App启动后,苹果会给RunLoop注册很多个observers,其中有两个是跟自动释放池相关的,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()

  • 第一个observer监听的是activities=0x1(kCFRunLoopEntry),也就是在即将进入loop时,其回调会调用_objc_autoreleasePoolPush() 创建自动释放池;

  • 第二个observer监听的是activities = 0xa0(kCFRunLoopBeforeWaiting | kCFRunLoopExit),即监听的是准备进入睡眠和即将退出loop两个事件。在准备进入睡眠之前,因为睡眠可能时间很长,所以为了不占用资源先调用_objc_autoreleasePoolPop()释放旧的释放池,并调用_objc_autoreleasePoolPush() 创建新建一个新的,用来装载被唤醒后要处理的事件对象;在最后即将退出loop时则会调用_objc_autoreleasePoolPop()释放池子。

    再即将进入loop时,会调用_objc_autoreleasePoolPush() 创建自动释放池;
    在准备睡眠时,先调用_objc_autoreleasePoolPop()释放旧的释放池,后调用_objc_autoreleasePoolPush() 创建新建一个新的,用来装载被唤醒后要处理的事件对象;
    在即将退出loop时,会调用_objc_autoreleasePoolPop()释放池子。

卡顿检测

可以通过RunLoop的不同状态来做页面刷新的卡顿检测。

线程常驻与线程保活

线程常驻

有的时候我们需要创建一个线程在后台一直做一些任务(比如比如后台播放音乐、下载文件等等,我们希望这条线程永远常驻内存),但是常规的线程在任务完成后就会立即销毁,因此我们需要一个常驻线程来让线程一直都存在。

- (void)viewDidLoad {[super viewDidLoad];self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];[self.thread start];
}- (void)run {NSRunLoop *currentRl = [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];//上面这段代码和下面的代码实现的效果相同,让runloop无限期运行下去
//    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];[currentRl run];
}- (void)run2
{NSLog(@"常驻线程");
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}

上面的代码就是一个常驻线程。把线程thread添加在到RunLoop中,通常RunLoop启动前必须要设置一个mode,并且为mode至少设置一个Source/Timer/Observer,在这里是添加了一个port,虽然消息可以通过port发送到RunLoop内,但是这里并没有发送任何的消息,所以这样便可以保持RunLoop不退出,s实现线程常驻。

NSRunLoop 需要至少一个输入源(如 NSPort)才能保持运行状态。如果 NSRunLoop 没有任何输入源,它会立即退出。通过添加一个 NSPort,可以确保 NSRunLoop 不会退出,从而使线程保持活跃状态。

线程保活

定义:线程保活是指一个线程在空闲时不立即销毁,而是保留一段时间以备后续任务使用。如果在保留时间内有新任务到来,线程可以重新被利用;如果超时未被利用,线程将被销毁。
我们不应该使用 run 方法来启动 RunLoop,因为它创建的是一个不会退出的循环,使用这个方法的子线程自然无法被销毁。我们可以像run 一样利用runMode:beforeDate: 方法来创建一个符合我们条件的子线程:

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

把它放到一个 while 循环中,利用一个是否停止 RunLoop 的全局标记来辅助处理线程的生命周期问题

__weak typeof(self) weakSelf = self;self.thread = [[LSThread alloc] initWithBlock:^{NSLog(@"func -- %s   thread -- %@", __func__, [NSThread currentThread]);[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] run];while (!weakSelf.isStopedThread) {[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}NSLog(@"--- 结束 ---");}];

停止 RunLoop

- (void)stop {self.isStopedThread = YES;CFRunLoopStop(CFRunLoopGetCurrent());
}

停止操作一定要在我们的目标线程执行,比如我们直接调用 stop 方法并不能达到我们预期的效果,这是因为stop 默认在主线程执行,没有拿到目标线程,停止无效。

两者的区别
生命周期管理:
线程常驻:线程一旦创建后会一直保持活动状态,不会退出,直到明确调用退出方法。适用于需要长期运行的后台任务。
线程保活:线程在空闲时会保留一段时间,等待新任务。在超时后,线程会被销毁。适用于短期任务较多但不需要长期保持线程的场景。

资源消耗:
线程常驻:会持续占用系统资源,即使在没有任务执行时也是如此。因此需要确保此类线程确实有长期任务以避免浪费资源。
线程保活:在空闲时资源占用较低,只有在有任务执行时才会占用系统资源。

实现复杂度:
线程常驻:相对简单,通过 NSRunLoop 或者循环机制可以轻松实现。
线程保活:需要更多的逻辑来管理线程的生命周期,如线程池、超时机制等。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com