iOS开发:浅尝辄止Runloop
Posted wuwuFQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发:浅尝辄止Runloop相关的知识,希望对你有一定的参考价值。
什么是Runloop
- 从字面意思就是运行循环
- 它内部就是
do-while
循环,在这个循环内部不断地处理各种任务 - 一个线程对应一个
Runloop
,主线程的Runloop
默认已经启动,子线程的Runloop
需要手动启动 Runloop
只能选择一个Mode启动,如果当前Mode中没有任何Source(Source0、Source1)、Timer,那么直接退出RunLoop
- 基本作用就是保持程序的持续运行,处理app的各种事件,通过
runLoop
,有事运行,没事休息,可以节省cpu资源,提高程序性能。 - OSX/ios 系统中,提供了两个这样的对象:
NSRunLoop
和CFRunLoopRef
。
CFRunLoopRef
是在CoreFoundation
框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop
是基于CFRunLoopRef
的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
Runloop与线程
- iOS 开发中能遇到两个线程对象:
pthread_t
和NSThread
,NSThread
只是pthread_t
的封装 - 每条线程都有唯一的一个与之对应的
RunLoop
对象,其关系是保存在一个全局的Dictionary
里。 - 主线程的
runloop
自动创建,子线程的runloop
默认不创建(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop];
获取RunLoop
对象的时候,就会创建RunLoop
); Runloop
寄生于线程:一个线程只能有唯一对应的runloop
;但这个根runloop
里可以嵌套子runloops;线程刚创建时并没有RunLoop
,如果你不主动获取,那它一直都不会有。RunLoop
的创建是发生在第一次获取时,RunLoop
的销毁是发生在线程结束时。
RunLoop怎么工作的
在 CoreFoundation 里面关于 RunLoop 有5个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopRef 与 CFRunLoopModeRef
// 结构体定义
struct __CFRunLoop
pthread_t _pthread; // runloop和线程是一一对应关系,每个runloop内部会保留一个对应的线程
CFMutableSetRef _commonModes; //标记为common的mode的集合
CFMutableSetRef _commonModeItems; //commonMode的item集合
CFRunLoopModeRef _currentMode; // 当前的模式
CFMutableSetRef _modes; // CFRunLoopModeRef类型的集合,相对NSArray有序,Set为无序集合
;
struct __CFRunLoopMode
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
;
一个 RunLoop
包含若干个 Mode,每个 Mode 又包含若干个 (set) Source/(array)Timer/(array)Observer
每次 RunLoop
启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode,如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
CFRunLoopModeRef
主要是用来指定事件在运行循环中的优先级
- NSDefaultRunLoopMode :默认,空闲状态的Mode
- UITrackingRunLoopMode:scrollView滑动时会切换到该Mode
- UIInitializationRunLoopMode :RunLoop启动时,会切换到该Mode
- GSEventReceiveRunLoopMode: Graphic相关事件的mode,通常用不到。
- NSRunLoopCommonModes :Mode集合
主线程里默认UITrackingRunLoopMode
, kCFRunLoopDefaultMode
被标记了Common,子线程中默认只有 kCFRunLoopDefaultMode
被标记。
CFRunLoop
对外暴露的管理 Mode 接口只有下面2个:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
Mode 暴露的管理 mode item 的接口有下面几个:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
苹果不允许直接创建 RunLoop
,它只提供了两个自动获取的函数:CFRunLoopGetMain()
和 CFRunLoopGetCurrent()
。
CFRunLoopSourceRef 是事件源(输入源)
即事件产生的地方, Source有两个版本:Source0 和 Source1。
按照函数调用栈的分类
1、Source0:非基于Port的 ,用于用户主动触发事件
2、Source1:基于Port的,通过内核和其它线程相互发送消息(触摸/锁屏/摇晃等)
struct __CFRunLoopSource
CFRuntimeBase _base;
uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被触发
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union
CFRunLoopSourceContext version0; // source0
CFRunLoopSourceContext1 version1; // source1
_context;
;
typedef struct
CFIndex version;
void * info;
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
CFRunLoopSourceContext;
typedef struct
CFIndex version;
void * info;
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
CFRunLoopSourceContext1;
CFRunLoopTimerRef 是基于时间的触发器
它和 NSTimer
是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop
时,RunLoop
会注册对应的时间点,当时间点到时,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; //执行的时间间隔
CFTimeInterval _tolerance; //时间偏差
uint64_t _fireTSR; // 触发时间
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; // 回调
CFRunLoopTimerContext _context; // 回调内容
;
CFRunLoopObserverRef 是观察者
每个 Observer 都包含了一个回调(函数指针),当 RunLoop
的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity)
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
;
struct __CFRunLoopObserver
CFRuntimeBase _base;
pthread_mutex_t _lock; // 线程锁
CFRunLoopRef _runLoop; // 所在的runloop
CFIndex _rlCount; // Observer监控的runloop数量 逻辑主要在__CFRunLoopObserverSchedule和__CFRunLoopObserverCancel
CFOptionFlags _activities; // RunLoop的几个状态 - immutable
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; // 回调
CFRunLoopObserverContext _context; // 回调的参数
;
监听runloop状态:
CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, -12222222, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity)
switch (activity)
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
case kCFRunLoopAllActivities:
NSLog(@"kCFRunLoopAllActivities");
break;
default:
NSLog(@"default");
break;
);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopDefaultMode);
RunLoop运行逻辑:图文释义
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle)
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime)
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
/// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop)
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// ? 一个基于 port 的Source 的事件。
/// ? 一个 Timer 到时间了
/// ? RunLoop 自身的超时时间到了
/// ? 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 9.收到消息,处理消息。
handle_msg:
/// 10.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer)
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
/// 10.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch)
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
/// 10.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop)
mach_msg(reply, MACH_SEND_MSG, reply);
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle)
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
else if (timeout)
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
else if (__CFRunLoopIsStopped(runloop))
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
else if (__CFRunLoopModeIsEmpty(runloop, currentMode))
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
while (retVal == 0);
/// 11. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
自己对RunLoop的理解
首先我们知道Runtime
是OC的底层,通过消息机制在运行时动态的确定调用方法和事件处理,那Runloop
充当什么角色呢?
程序启动,main
函数会在主线程启动一个mainLoop
,上图是一个按钮的点击事件,自下而上可以看到Runloop
的大概脉络,接收到了Source0
的事件源,处理block
时,通过 Runtime
找到 isa
的class
和 IMP
响应方法(这块涉及 Runtime 和 事件传递 的知识),然后runloop
进入休眠等待唤醒,mainLoop
不会退出,子线程的runloop
当没有 Source / Timer
时就会退出,结束线程。
网上很多分析和讲解Runloop底层的,唯有ibireme大神讲解的入骨三分,大家可以拜读一下。
以上是关于iOS开发:浅尝辄止Runloop的主要内容,如果未能解决你的问题,请参考以下文章