Runloop

Runloop

什么是runloop

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

Event Loop:

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

runloop对象

CoreFoundation-CFRunloop
Foundation - NSRunloop
NSRunLoop实际上是CFRunLoop的高层抽象,CFRunloop是线程安全的,NSRunloop非线程安全.
CFRunloop是开源的:https://opensource.apple.com/source/CF/CF-1151.16/

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

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

开启runloop

- (void)run;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
- (void)runUntilDate:(NSDate *)limitDate;

run方法对应上面CFRunloopRef中的CFRunLoopRun并不会退出,除非调用CFRunLoopStop();通常如果想要永远不会退出RunLoop才会使用此方法,否则可以使用runUntilDate。
runMode:beforeDate:则对应CFRunLoopRunInMode(mode,limiteDate,true)方法,只执行一次,执行完就退出;通常用于手动控制RunLoop(例如在while循环中)。
runUntilDate:方法其实是CFRunLoopRunInMode(kCFRunLoopDefaultMode,limiteDate,false),执行完并不会退出,继续下一次RunLoop直到timeout。

runloop与线程

一般来说,线程同一时间只能执行一个任务,任务执行完毕线程就会被销毁.每条线程都有唯一的一个与之对应的RunLoop对象。RunLoop 和线程的一一对应关系保存一个全局 Dictionary 里 __CFRunLoops.主线程的RunLoop是自动创建的(使主线程不被销毁保证了程序的运行而不退出),子线程的RunLoop需要手动创建CFRunLoopGetCurrent(),在第一次获取时创建,在线程结束时销毁。苹果未提供手动创建runloop的API,

在UIApplicationMain函数中,实际就是开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。

Runloop机制

Core Foundation中关于RunLoop的5个类:

  • CFRunLoopRef //获得当前RunLoop和主RunLoop
  • CFRunLoopModeRef //运行模式,只能选择一种,在不同模式中做不同的操作,且运行必须有指定的Mode
  • CFRunLoopSourceRef //事件源,输入源
  • CFRunLoopTimerRef //定时器时间
  • CFRunLoopObserverRef //观察者

CFRunLoopRef

typedef struct __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
pthread_t _pthread; //和线程一对一的关系
uint32_t _winthread;
CFMutableSetRef _commonModes; // 字符串,记录所有标记为common的mode
CFMutableSetRef _commonModeItems; // 所有commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes; // CFRunLoopModeRef set
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};

• CFRunLoop 里面包含了线程,若干个 mode。
• CFRunLoop 和线程是一一对应的。
• _blocks_head 是 perform block 加入到里面的

CFRunLoopMode

// 定义 CFRunLoopModeRef 为指向 __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; // source0 set ,非基于Port的,接收点击事件,触摸事件等APP 内部事件
CFMutableSetRef _sources1; // source1 set,基于Port的,通过内核和其他线程通信,接收,分发系统事件
CFMutableArrayRef _observers; // observer 数组
CFMutableArrayRef _timers; // timer 数组
CFMutableDictionaryRef _portToV1SourceMap;// source1 对应的端口号
__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 */
};

通过CFRunloopRef对应结构体的定义可以知道每种Runloop都包含若干个(1:N)Mode,每个Mode又包含若干(1:N)Source/Timer/Observer。Runloop总是运行在某种特定的CFRunLoopModeRef下,当切换Mode时必须退出当前Mode,然后重新进入Runloop以保证不同Mode的Source/Timer/Observer互不影响。

系统默认提供的Run Loop Modes有kCFRunLoopDefaultMode(NSDefaultRunLoopMode)和UITrackingRunLoopMode,需要切换到对应的Mode时只需要传入对应的名称即可。前者是系统默认的Runloop Mode,例如进入iOS程序默认不做任何操作就处于这种Mode中,此时滑动UIScrollView,主线程就切换Runloop到到UITrackingRunLoopMode,不再接受其他事件操作(除非你将其他Source/Timer设置到UITrackingRunLoopMode下)。
kCFRunLoopCommonModes(NSRunLoopCommonModes),这个并不是某种具体的Mode,而是一种模式组合,在iOS系统中默认包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode,是同步Source,Timer,Observer到多个Mode中的一种技术方案(注意:并不是说Runloop会运行在kCFRunLoopCommonModes这种模式下,而是相当于分别注册了 NSDefaultRunLoopMode和 UITrackingRunLoopMode。当然你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到 kCFRunLoopCommonModes组合)。

RunLoopSource

runloop3.jpg

RunLoopSource 分为 source0 和 source1。

  • source0 是非基于 port 的事件,主要是 APP 内部事件,如点击事件,触摸事件等 需要手动唤醒线程
  • source1 是基于Port的,通过内核和其他线程通信,接收,分发系统事件。具备唤醒线程的能力
  • CFRunLoopSource 里面包含一个 _runLoops,也就意味着一个 CFRunLoopSource 可以被添加到多个 runloop mode 中去。
  • Source1在处理的时候会分发一些操作给Source0去处理
    Source0(负责App内部事件,由App负责管理触发,例如UITouch事件)和Timer(又叫Timer Source,基于时间的触发器,上层对应NSTimer)是两个不同的Runloop事件源(当然Source0是Input Source中的一类,Input Source还包括Custom Input Source,由其他线程手动发出),RunLoop被这些事件唤醒之后就会处理并调用事件处理方法(CFRunLoopTimerRef的回调指针和CFRunLoopSourceRef均包含对应的回调指针)。
    Source1除了包含回调指针外包含一个mach port,和Source0需要手动触发不同,Source1可以监听系统端口和其他线程相互发送消息,它能够主动唤醒RunLoop(由操作系统内核进行管理,例如CFMessagePort消息)。官方也指出可以自定义Source,因此对于CFRunLoopSourceRef来说它更像一种协议,框架已经默认定义了两种实现,如果有必要开发人员也可以自定义.

RunLoopTimer

  • CFRunLoopTimer 是基于事件的定时器,可以在设定的时间点抛出回调
  • CFRunLoopTimer和NSTimer是toll-free bridged的,可以相互转换。

CFRunLoopObserver

观察者,可以观察RunLoop的各种状态,并抛出回调。可以监听的状态如下:

/* Run Loop Observer Activities */
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 //所有状态
};

我们可以手动创建一个CFRunLoopObserver来监听Runloop的各种状态

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFStringRef runLoopMode = kCFRunLoopDefaultMode;
//参数1:构造器
//参数2:监听的CFRunLoopActivity(位移型枚举)活动状态,kCFRunLoopAllActivities代表所有状态
//参数3:是否每次都需要监听
//参数4:优先级
//参数5:回调函数
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入 RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理 Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理 Source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
;
break;
case kCFRunLoopAfterWaiting:
NSLog(@"从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将退出Loop");
break;
default:
break;
}
});
//添加observer到runloop对象,来监听指定的Mode
CFRunLoopAddObserver(runLoop, observer, runLoopMode);
CFRelease(observer);

我们经常可以在调用堆栈看到以下函数

//Observer回调
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
//CFRunLoopPerformBlock回调
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
//dispatch_getMain
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
//timer,NSTimer...
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
//source0比如点击事件
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
{
/// 1. 通知Observers,即将进入RunLoop
/// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {

/// 2. 通知 Observers: 即将触发 Timer 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

/// 4. 触发 Source0 (非基于port的) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

/// 6. 通知Observers,即将进入休眠
/// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);

/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();


/// 8. 通知Observers,线程被唤醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);

/// 9. 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);

/// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);

/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);


} while (...);

/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}

CFRunLoopRun

kenshincui

ibireme
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

/// 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;
}

/// 通知 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);

/// 收到消息,处理消息。
handle_msg:

/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}

/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}

/// 9.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);
}

/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

参考:
https://blog.ibireme.com/2015/05/18/runloop/
https://www.cnblogs.com/kenshincui/p/6823841.html
http://honglu.me/2017/03/30/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3RunLoop/
http://www.imlifengfeng.com/blog/?p=487

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容