Runloop 是 iOS 和 OSX 中和线程相关的基础概念。提供了线程循环处理事件的能力,当有事件处理时唤醒线程工作,空闲时使线程进入休眠,以合理的利用系统资源。
Runloop 是线程相关的概念,和线程是一一对应的。一个线程对应一个runloop,默认情况下,主线程的 runloop 是启动的,后台线程的 runloop 需要自己启动。
Runloop 的工作模型类似 windows 中的消息机制(Event loop),接受事件(消息)->处理->等待新事件(消息),伪代码如下:
loop()
{
init();
while(get_message(&msg) != quit){
handle_message(msg);
}
}
Runloop 中需要处理的事件分2类:
- 输入源(input source):分发异步事件,通常都是来自其他线程或者Application。
- performSelector源
- 基于端口的源(mach port)
- 自定义输入源
- 计时器:定时的或者一定间隔的触发同步事件。
Runloop 的运行结构图如下图:

Runloop 其实是一个对象,提供了一个入口函数来执行上面的事件循环,在函数内部实现了接受消息->处理->等待新消息
这样的一个循环流程,直到循环结束。
Runloop 运行中会发出许多通知,比如状态的改变,可以监听这些通知作对应的处理。
iOS 和 OSX 中提供了2个这样的对象:NSRunloop和CFRunLoopRef。
内部逻辑大致如下图:

运行模式
运行模式定义了一个集合,当 Runloop 运行在某个模式下时,只会监听某些类型的输入源事件和计时器事件,以及分发某些特定种类的通知,启动 Runloop 时会指定一种模式。
常见的一些运行模式:
- NSDefaultRunLoopMode:默认模式,包括了大部分的操作事件,绝大部份情况下都应该使用这个模式配置自己的 runloop
- NSEventTrackingRunLoopMode:典型的当 scrollView 滚动的时候是运行在该模式下,此时的不接受Timer事件,所以Timer会失效
- NSRunLoopCommonModes:模式组,一个模式将自己标记为“common”属性后就会在该组中,运行在该模式下,能处理的事件就是组中各个模式能处理事件的合集,在默认情况下,NSDefaultRunLoopMode 和NSEventTrackingRunLoopMode 被标记为 “common” 属性
输入源
- 基于端口的源:监听Mach port,cocoa 提供了内置的类来实现这类输入源 NSPort (IPC相关)
- 自定义输入源:
- PerformSelector源:主要是那些和时间能扯上关系的API,比如:(其实是通过timer实现的)
- performSelectorOnMainThread:withObject:waitUntilDone:
- performSelector:withObject:afterDelay:
- performSelector:onThread:withObject:waitUntilDone:
Runloop 的使用
- 使用端口或者自定义输入源来实现线程间的通信
- 在线程中使用计时器
- 使用performSelector方法
- 保持线程执行周期性的任务
Runloop 的应用
- Autorelease pool:runloop 会自动维护autorelease pool的创建和释放,autorelease pool 会在下一次runloop循环之前释放 pool 中的对象,所以在像for,while这样的循环中,如果需要大量创建临时对象时需要自己创建autorelease pool,避免临时内存峰值过高,比如图片相关的操作
- 事件响应(触摸/锁屏/摇晃等):source1 事件
- 手势识别:监听特定的通知,处理手势的回调
- 界面更新:监听特定的通知,处理界面的刷新操作
- 定时器:NSTimer 和 CADisplayLink
- PerformSelector:AfterDelay:
- GCD:dispatch_async(dispatch_get_main_queue(), block)
附 Runloop 的部分源码图:

一个使用 Runloop 的简单例子
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
网友评论