前言
2016年6月7号开始编写CFRunLoop,并通过实现代码
NSRunLoop
苹果提供NSRunLoop类来实现RunLoop机制,NSRunLoop类声明的对象,用于管理输入源对象。一个NSRunLoop对象可以监听输入源包括鼠标、键盘事件,以及NSPort、NSConnection对象,以及NSRunLoop对象还可以处理NSTimer事件
应用程序不能创建或显式管理NSRunLoop对象。每个NSThread对象以及应用程序的主线程都会根据自身需要自动创建它,如果想要访问当前线程的RunLoop可以调用类方法+currentRunLoop,
NSRunLoop方法不是线程安全的,它的方法应该只存在当前线程的上下文,使用时应该避免NSRunLoop对象运行在不同线程,否则可能会导致意想不到的结果(CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。)
NSRunLoop使用方法
访问NSRunLoop属性和方法:
currentMode
+ currentRunLoop
- limitDateForMode:
+ mainRunLoop
- getCFRunLoop
管理计时器:
- addTimer:forMode:
管理端口
- addPort:forMode
- removePort:forMode:
运行一个循环
- (void)run
- 将接收到一个持久循环,在此期间它处理来自附加到输入数据源的数据。
Discussion:
- 如果没有连接的输入源或计时器,运行RunLoop,该方法立即退出;否则,将通过runMode:beforeDate:方法设置Mode为NSDefaultRunLoopMode添加至接收器上。换句话说如果run方法有效,则开始无限循环运行处理传入的输入源和计时器。
- runMode:beforeDate:在指定模式下运行该循环,直到给定日期为止,返回True 运行循环运行和处理输入源,如果是False,如果指定的超时值达到时,Run Loop将无法启动
Discussion:
-
如果没有连接的输入源或计时器运行RunLoop,这种方法会立即退出并返回NO;否则,它返回后第一个输入源进行处理或达到limitdate超时,手动删除所有已知的输入源和计时器并且保证运行循环不会立即退出
- (void)runUntilDate:(NSDate *)limitDate:运行循环,直到指定日期,在此期间,它处理来自附加输入源的数据。
Discussion:
- (void)runUntilDate:(NSDate *)limitDate:运行循环,直到指定日期,在此期间,它处理来自附加输入源的数据。
如果没有连接的输入源或计时器运行RunLoop,该方法会立即退出并返回NO;否则,它运行的接收机NSDefaultRunLoopMode通过反复调用runMode:beforeDate:直到指定的截止日期,手动删除所有已知的输入源和计时器并且保证运行循环不会立即退出
终止循环
Example:
在需要终止的地方调用shouldKeepRunning为NO
BOOL shouldKeepRunning = YES; // global
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
限制RunLoop终止时间
- (BOOL)runMode(NSString *)mode beforeDate:(NSDate *)limitDate
Discussion:
- 如果没有输入源或计时器连接到RunLoop,这个方法会立即退出并返回NO;否则,它将处理第一个输入源直到达到终止时间limitDate。
- 手动删除所有RunLoop已知的输入源和计时器并不保证运行循环立即退出。
- 注意:计时器不被视为输入源,可能在等待该方法时返回可能触发多次
RunLoop调度和取消消息
// 此方法设置一个定时器,在下次运行循环开始执行aselector消息接收器,指定Mode参数设置定时器在该模式中运行
- performSelector:target:argument:order:modes:
Discussion
- 该方法设置一个计时器,在下次RunLoop时开始执行aselector消息接收器。
- 计时器被配置为所述模式参数中指定的模式下运行。当计时器触发时,线程试图从RunLoop列出消息,并进行选择。如果成功触发,RunLoop运行,并且在指定模式中。否则,计时器等待,直到RunLoop是在这些模式之一。
// 使用此方法取消的消息先前预定的使用的performSelector:target:argument:order:modes:,方法从运行循环的所有模式中移除执行请求。
- cancelPerformSelector:target:argument:
// 此方法取消与目标关联的原定信息,忽略了对调度操作的选择和参数。
- cancelPerformSelectorsWithTarget:
Run Loop Mode
NSRunLoop定义以下RunLoopMode运行循环模式。
处理除NSConnection以外的对象的输入源模式。
NSDefaultRunLoopMode:当 UI 处于空闲状态时,默认的 Mode 是 NSDefaultRunLoopMode(同 CF 中的 kCFRunLoopDefaultMode),同时也是 CF 中定义的 “空闲状态 Mode”。当用户啥也不点,此时也没有什么网络 IO 时,就是在这个 Mode 下。
NSRunLoopCommonModes:作为一组“共同”模式(组合Mode),使用该值运行监控的所有Run Loop Mode;
extern NSString* const NSDefaultRunLoopMode;
extern NSString* const NSRunLoopCommonModes;
CFRunLoop
CFRunLoop对象监控输入任务的来源和分派已处理的任务对它进行控制,输入源包括user input device(用户输入设备)、network connections(网络连接), periodic or time-delayed events(周期或时滞事件), and asynchronous callbacks(异步回调).
提供三种类型的对象监视Run Loop
- 数据源对象:CFRunLoopSource
- 计时器对象:CFRunLoopTimer
- 观察者对象:CFRunLoopObserver
处理这些对象时接收回调需要处理,首先将这些对象放到一个 RunLoop。
- CFRunLoopAddSource
- CFRunLoopAddTimer
- CFRunLoopAddObserver
(1)每个源、计时器和观察者添加到Run Loop必须指定一个或多个运行循环模式(Run Loop Mode)相关联。
(2)模式确定后,在给定的迭代过程中RunLoop处理哪些事件。每次运行循环执行时,它会在特定模式下运行。在这种模式下,运行循环过程只处理与源、计时器、观察者与该模式相关联的事件。
(3)默认运行循环方式由kCFRunLoopDefaultMode常数指定,当应用程序(或线程)处于闲置状态时处理事件。
(4)RunLoop定义了其他的模式,并且可以在其他模式中执行运行循环,以限制源、计时器和观察员被处理(例如以pseudo-mode为核心基础,称为公共模式,允许将多个模式与给定的源、计时器或观察者关联起来),还可以定义自己的自定义模式来限制事件处理,如果指定的是其他模式,可能处理滑动事件(UITableView/UIScrollView)时,限制其他模式(例如网络IO处理)。
(5)添加一个模式的常见模式,使用CFRunLoopAddCommonMode函数。
功能
获得一个运行循环
CFRunLoopGetCurrent:返回当前线程CFRunLoop对象
Discussion:
- 每个线程都有一个与之关联的RunLoop
CFRunLoopGetMain:返回主CFRunLoop对象。
启动和停止循环
CFRunLoopRun:在当前线程的使用默认模式运行
Discossion:
- 当前线程在默认模式(CFRunLoopDefaultMode)运行RunLoop,直到运行循环所有数据源和计时器是从默认的运行循环模式移动或者调用CFRunLoopStop函数停止RunLoop。
- 运行循环可以递归运行,可以从任何运行循环标注内调用CFRunLoopRun,并且当前线程调用栈上创建嵌套运行循环激活。
CFRunLoopRunResult CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );当前线程的CFRunLoop对象运行在一个特定的模式。
Parameters:
- mode
指定RunLoopMode下运行。模式可以是任意CFString字符串。你并不需要显式地创建一个运行循环模式,虽然运行循环模式需要包含至少一个源或定时器运行。 - seconds
设置时间的长短来运行RunLoop。如果为0,只有一个能通过RunLoop然后返回;;如果多个数据源或计时器时准备立即触发,如果是0时无意义 - returnAfterSourceHandled
指示在处理一个源后运行循环是否应该退出的标志。如果为false,运行循环继续,直到秒钟已经过去了处理事件。
Discussion:
- 运行循环可以递归运行。可以从任何运行循环标注内调用CFRunLoopRunInMode和当前线程的调用堆栈中创建嵌套运行循环激活。并不局限于在模式就可以运行在一个标注。可以在任何可用的运行循环模式创建另一个运行的循环运行激活,包括任何模式已经运行在调用堆栈中较高的。(简而言之:设置该Mode运行RunLoop优先级为最高),之所以这里详细说明是后面阅读CFRunLoop源码有这一块内容,方便理解。
返回码为CFRunLoopRunInMode,确定运行循环退出的原因,以下返回值:
kCFRunLoopRunFinished。运行循环模式模式没有源或计时器。
kCFRunLoopRunStopped。运行循环停止调用CFRunLoopStop函数。
kCFRunLoopRunTimedOut。时间间隔过去了(运行RunLoop超时)。
kCFRunLoopRunHandledSource。源已经被处理。这种退出条件,只有在RunLoop运行循环被告知只运行到一个源处理。
- 不能指定kCFRunLoopCommonModes常数模式作为参数,运行循环总是在特定的模式下运行。
- 在配置一个运行循环的观察者想要的观察员和希望在多个模式下运行的情况下,才指定的共同模式kCFRunLoopCommonModes。
CFRunLoopWakeUp:唤醒一个等待CFRunLoop对象
CFRunLoopStop:强制CFRunLoop对象停止运行。
CFRunLoopIsWaiting:返回一个布尔值来指示运行循环等待一个事件,如果是True,rl没有事件要处理和阻塞,等待一个源或计时器处于就绪状态;如果为False,rl要么不运行或正在处理的源程序,计时器,或观察者。
管理观察
CFRunLoopAddObserver 添加一个CFRunLoopObserver对象运行循环模式。
Discussion:
一个运行循环的观察者只能在一次运行循环进行登记,它可以被添加到运行的循环中的多个运行循环模式。
如果rl已经包含在模式观察者,这个函数什么也不做。CFRunLoopContainsObserver:返回一个布尔值,指示RunLoopMode是否包含一个特定的CFRunLoop观察对象。
CFRunLoopRemoveObserver:移除RunLoopMode运行循环模式下的CFRunLoop观察对象。
管理运行循环模式
CFRunLoopAddCommonMode:添加一个模式到设定的RunLoop的comomon modes(组合模式),RunLoopMode添加到Common modes模式,它不能被删除。
CFRunLoopCopyAllModes:返回一个数组,其中包含所有CFRunLoop对象定义的模式。
CFStringRef CFRunLoopCopyCurrentMode ( CFRunLoopRef rl ):返回一个给定的运行循环当前正在运行的模式的名称
管理计时器
void CFRunLoopAddTimer ( CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode ):添加一个CFRunLoopTimer对象到运行循环模式。
CFAbsoluteTime CFRunLoopGetNextTimerFireDate ( CFRunLoopRef rl, CFStringRef mode ):返回的时间是下一个就绪计时器
CFRunLoopRemoveTimer ( CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode ):从一个运行循环模式,删除一个CFRunLoopTimer对象。
CFRunLoopContainsTimer ( CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode ):返回一个布尔值,用于显示运行循环模式是否包含一个特定CFRunLoopTimer对象。
调度模块
void CFRunLoopPerformBlock ( CFRunLoopRef rl, CFTypeRef mode, void (^block)(void) ):排入给定RunLoop ,在指定的RunLoopMode下runloop周期执行block任务。
- 当runloop在指定的模式下运行时,执行该块的对象,可以使用此功能为手段,以移除当前的RunLoop操作通过指定到Cocoa框架下performSelector:onThread:withObject:waitUntilDone:方法另一个线程 和相关方法,还可以使用它作为一种替代机制,如把一个CFRunLoopTimer在另一个线程进行运行循环,或使用CFMessagePort线程之间传递信息。
线程与运行循环
- 运行循环是与线程关联的基础设施的一部分。运行循环是一个事件处理循环,用于调度工作,并协调接收传入的事件。运行循环目的是让你的线程在有工作的时候很忙,把你的线程放在没有工作的时候休眠。
- 运行循环管理并不是完全自动的。还必须设计出你的线程的代码,在适当的时间启动运行循环,并响应传入的事件。Cocoa和Core Foundation提供运行循环对象以帮助配置和管理线程的运行循环。应用程序不需要显式地创建这些对象,每一个线程,包括应用程序的主线程,都有一个相关的运行循环对象。只有辅助线程需要明确地运行他们的运行循环。该应用程序框架自动设置和运行在主线程中运行循环的应用程序作为启动过程的一部分。
RunLoop代码逻辑
根据苹果官方文档对RunLoop代码逻辑是这么描述的
A run loop is very much like its name sounds. It is a loop your thread enters and uses to run event handlers in response to incoming events. Your code provides the control statements used to implement the actual loop portion of the run loop—in other words, your code provides the while
or for
loop that drives the run loop. Within your loop, you use a run loop object to "run” the event-processing code that receives events and calls the installed handlers.
- 运行循环很像它的名字听起来。 它是一个循环,线程进入和使用事件处理程序来响应传入的事件。 代码提供了控制语句用于实现的实际循环部分运行循环——换句话说,您的代码提供了while或for循环来驱动运行循环。 在循环内,使用一个运行循环对象“运行”接收事件的处理代码,并调用已安装的处理程序。
代码逻辑是这样的:
- (void)loop {
initialize()
while (message != quit) {
// 获取唤醒事件
id message = get_next_message()
// 处理事件
process_message(message)
}
}
- get_next_message()程序通常由操作系统提供,直到一个信息是可用的,循环只在输入时有东西要处理才执行。
RunLoop的概念结构和各种各样的来源如下图所示。 输入源提供异步事件到相应的处理程序,并造成runUntilDate:方法(称为线程的相关NSRunLoop对象)(runUntilDate可以查阅NSRunLoop方法Discussion介绍)退出。 计时器来源提供事件处理程序,不会引起循环运行退出。
除了处理源的输入外,运行循环也会产生关于循环行为的通知。注册的运行循环的观察员可以接收这些通知,并使用它们在线程中进行附加处理。
Run Loop Observers
当一个适当的异步或同步事件发生时,在特定位置的Run Loop Observers执行循环过程中,可以使用Run Loop Observers给准备线程或准备在休眠之前线程处理一个给定的事件。在运行循环中与下列事件关联来观察Run Loop Observers:
- 即将进入运行循环。
- 当运行循环将要处理一个计时器时。
- 当运行循环将要处理一个输入源时。
- 当运行循环将要休眠。
- 当运行循环被唤醒时,在被唤醒之前,处理唤醒事件
- 退出运行循环。
苹果官方提供CFRunLoopActivity枚举来观察RunLoop活动阶段
enum CFRunLoopActivity {
kCFRunLoopEntry = (1 << 0), // 即将进入运行循环
kCFRunLoopBeforeTimers = (1 << 1), // 当运行循环将要处理一个计时器时。
kCFRunLoopBeforeSources = (1 << 2), // 当运行循环将要处理一个输入源时。
kCFRunLoopBeforeWaiting = (1 << 5), // 当运行循环将要休眠。
kCFRunLoopAfterWaiting = (1 << 6), // 当运行循环被唤醒时,在被唤醒之前,处理唤醒事件
kCFRunLoopExit = (1 << 7), // 退出运行循环。
kCFRunLoopAllActivities = 0x0FFFFFFFU // 结合之前的所有阶段
};
typedef enum CFRunLoopActivity CFRunLoopActivity;
通常使用CFRunLoopObserverCreate、CFRunLoopObserverCreateWithHandler来监听RunLoop状态,接收回调信息(常见于自动释放池创建销毁)
例子:
//
// ConfiguringRunLoopExample.m
// RunLoopExample
//
// Created by lmj on 16/6/7.
// Copyright © 2016年 linmingjun. All rights reserved.
//
#import "ConfiguringRunLoopExample.h"
@interface ConfiguringRunLoopExample ()
@end
@implementation ConfiguringRunLoopExample
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(threadMain) toTarget:self withObject:nil];
// [self threadMain];
// [self performSelectorInBackground:@selector(threadMain) withObject:nil];
// [self performselector]
// [self performSelector:@selector(threadMain) withObject:nil afterDelay:0];
}
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
// 获得当前thread的Run loop
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// typedef void (*myObserver)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
// Create a run loop observer and attach it to the run loop.
// 设置Run loop observer的运行环境
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
// 创建Run loop observer对象
// 第一个参数用于分配observer对象的内存
// 第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释
// 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
// 第四个参数用于设置该observer的优先级
// 第五个参数用于设置该observer的回调函数
// 第六个参数用于设置该observer的运行环境
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver,&context);
// CFRunLoopObserverRef observer2 = CFRunLoopObserverCreateWithHandler
// (kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// //The entrance of the run loop, before entering the event processing loop.
// //This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
// });
if (observer)
{
// 将Cocoa的NSRunLoop类型转换成Core Foundation的CFRunLoopRef类型
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
// 将新建的observer加入到当前thread的run loop
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);
}
- (void)doFireTimer:(NSTimer *)timer {
NSLog(@"current thread: %@",[NSThread currentThread]);
NSLog(@"doFireTimer, %f", timer.timeInterval);
}
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
//The entrance of the run loop, before entering the event processing loop.
//This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopEntry:
NSLog(@"run loop entry");
break;
//Inside the event processing loop before any timers are processed
case kCFRunLoopBeforeTimers:
NSLog(@"run loop before timers");
break;
//Inside the event processing loop before any sources are processed
case kCFRunLoopBeforeSources:
NSLog(@"run loop before sources");
break;
//Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire.
//This activity does not occur if CFRunLoopRunInMode is called with a timeout of 0 seconds.
//It also does not occur in a particular iteration of the event processing loop if a version 0 source fires
case kCFRunLoopBeforeWaiting:
NSLog(@"run loop before waiting");
break;
//Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up.
//This activity occurs only if the run loop did in fact go to sleep during the current loop
case kCFRunLoopAfterWaiting:
NSLog(@"run loop after waiting");
break;
//The exit of the run loop, after exiting the event processing loop.
//This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopExit:
NSLog(@"run loop exit");
break;
/*
A combination of all the preceding stages
case kCFRunLoopAllActivities:
break;
*/
default:
break;
}
}
@end
Parameters
** allocator **
- 分配器用来为新对象分配内存。传递NULL或kCFAllocatorDefault使用当前默认的分配。
activities
- 设置标志来标识运行循环,在此期间观测被称为活性阶段。
repeats
- 一个标志识别观察者是否只调用一次,或多次运行循环。如果repeats为false,它被称为一次之后,观察者是无效的,即使观察者被安排在运行循环中的多个阶段调用。
order
- 一个优先级索引,指示运行循环观察者的顺序。一般为0
**block **
- 设置该observer的回调函数
observer
- 正在执行的运行循环观察器。
**activity **
- 循环的当前活动阶段。
RunLoop 内部的逻辑:
每次运行它,线程的RunLoop处理等待事件,并生成通知附加到观察者上。它的顺序具体执行顺序如下图