配置Run Loop源
以下部分显示了如何在Cocoa和Core Foundation中设置不同类型的输入源的示例。
定义自定义输入源
创建自定义输入源包括定义以下内容:
您希望输入源处理的信息。
一个调度程序,让感兴趣的客户知道如何联系您的输入源。
执行任何客户端发送请求的处理程序。
取消例程,使您的输入源无效。
因为创建一个自定义输入源来处理自定义信息,所以实际的配置被设计为灵活的。调度程序,处理程序和取消例程是您自定义输入源几乎总是需要的关键例程。然而,大多数输入源行为的其余部分发生在这些处理程序例程之外。例如,您可以定义将数据传递到输入源的机制,并将输入源的存在传达给其他线程。
图3-2显示自定义输入源的示例配置。在这个例子中,应用程序的主线程维护对输入源的引用,该输入源的自定义命令缓冲区以及安装了输入源的运行循环。当主线程有一个任务需要切换到工作线程时,它将一个命令发送到命令缓冲区以及工作线程启动任务所需的任何信息。(因为主线程和工作线程的输入源都可以访问命令缓冲区,所以该访问必须被同步)。一旦命令被发布,主线程将通知输入源并唤醒工作线程的运行循环。在接收到唤醒命令后,运行循环将调用输入源的处理程序,处理命令缓冲区中发现的命令。
在运行循环上安装输入源
- (void)configRunLoop{
// 子线程
_thread = [NSThread currentThread];
// 上下文
CFRunLoopSourceContext _source_context;
// 初始化方法1 跟下面初始化方法一样 用哪一种都可以
bzero(&_source_context, sizeof(_source_context));
//初始化方法 2
// CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
//这里创建了一个基于事件的源 void (*perform)(void *info);这个perform是一个回调函数,这里就是我们将要执行的函数
_source_context.perform = fire11;
// 创建soucre
_source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
//将源添加到当前RunLoop中去
CFRunLoopAddSource(CFRunLoopGetCurrent(), _source, kCFRunLoopCommonModes);
while (!_shouldStop) {
NSLog(@"RunLoop 开始运行");
//每次RunLoop只运行10秒,每10秒做一次检测,如果没有需要处理的后台任务了,就让此线程自己终止,不用暴力Kill
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, NO);
NSLog(@"RunLoop 停止运行");}
}
static void fire11(void * info __unused)
{
NSLog(@"我现在正在处理后台任务");
}
-(void)test
{
// 创建子线程
[self performSelectorInBackground:@selector(configRunLoop) withObject:nil];
// 开始事件
UIButton* __button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[__button1 setTitle:@"Fire Event" forState:UIControlStateNormal];
//触发事件启动RunLoop
[__button1 addTarget:self action:@selector(triggerEvent) forControlEvents:UIControlEventTouchUpInside];
__button1.frame = CGRectMake(0, 0, 100, 80);
[self.view addSubview:__button1];
UIButton* __button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[__button2 setTitle:@"Stop RunLoop" forState:UIControlStateNormal];
//RunLoop周期完成后自动退出线程
[__button2 addTarget:self action:@selector(stopRunloop) forControlEvents:UIControlEventTouchUpInside];
__button2.frame = CGRectMake(110, 0, 120, 80);
[self.view addSubview:__button2];
}
信号输入源
在将数据交给输入源后,客户端必须向源发出信号并唤醒其运行循环。信令源使得运行循环知道源可以被处理。并且因为线程可能在信号发生时睡着了,所以你应该总是明确地唤醒运行循环。否则可能导致处理输入源的延迟。
- (void)triggerEvent
{
if (CFRunLoopIsWaiting(CFRunLoopGetCurrent())) {
NSLog(@"RunLoop 正在等待事件输入");
//添加输入事件
CFRunLoopSourceSignal(_source);
//唤醒线程,线程唤醒后发现由事件需要处理,于是立即处理事件
CFRunLoopWakeUp(CFRunLoopGetCurrent());}
else {
NSLog(@"RunLoop 正在处理事件");
//添加输入事件,当前正在处理一个事件,当前事件处理完成后,立即处理当前新输入的事件
CFRunLoopSourceSignal(_source);}
}
注释:创建一个自定义输入源添加到Run Loop里面,默认是Run Loop每10秒钟检测一次,因为还没有点击按钮,所以没有被唤醒,不能执行fire11函数,点击之后会唤醒,就会执行fire11。因为Run Loop运行在CFRunLoopRunInMode 一个特定模式下,点击关闭按钮的时候,会执行完最后一轮10秒之后会退出Run Loop。
配置定时器源
要创建计时器源,您只需创建一个计时器对象并在运行循环中进行计划。在Cocoa中,您可以使用NSTimer该类来创建新的定时器对象,并且在Core Foundation中使用CFRunLoopTimerRef不透明类型。在内部,NSTimer该类只是Core Foundation的扩展,它提供了一些方便的功能,例如使用相同方法创建和计划定时器的能力。
在Cocoa中,您可以使用以下任一类方法一次创建和计划计时器:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:
这些方法创建定时器,并将其添加到默认mode(NSDefaultRunLoopMode)中的当前线程的运行循环中。如果您想通过创建NSTimer对象然后使用addTimer:forMode:方法将其添加到运行循环中,也可以手动安排计时器NSRunLoop。这两种技术基本上都是一样的,但是给你不同程度的控制定时器的配置。例如,如果创建定时器并手动将其添加到运行循环中,则可以使用除默认模式之外的模式来执行此操作。清单3-10显示了如何使用这两种技术创建定时器。第一个计时器的初始延迟时间为1秒,然后每隔0.1秒定时触发。第二个定时器在初始0.2秒延迟之后开始触发,然后每0.2秒触发一次。
-(void)timerMain
{
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:10.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:@selector(myDoFireTimer1:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:@selector(myDoFireTimer2:)
userInfo:nil
repeats:YES];
}
-(void)myDoFireTimer1:(id)timer
{
NSLog(@"myDoFireTimer1");
}
-(void)myDoFireTimer2:(id)timer
{
NSLog(@"myDoFireTimer2");
}
注释:上面是平时使用的计时器就不多解释了。
使用Core Foundation函数配置定时器所需的代码。虽然此示例不会在上下文结构中传递任何用户定义的信息,但您可以使用此结构传递定时器所需的任何自定义数据。有关此结构的内容的更多信息,请参阅CFRunLoopTimer参考中的描述。
// 使用Core Foundation创建和计划定时器
void myCFTimerCallback(CFRunLoopTimerRef timer, void *info)
{
NSLog(@"myCFTimerCallback");
}
-(void)corefoundationTimer
{
// 获取当前runloop
CFRunLoopRef runLoop =CFRunLoopGetCurrent();
// 上下文
CFRunLoopTimerContext context = {0,NULL,NULL,NULL,NULL};
// 创建timer
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,0.1,0.3,0,0,
&myCFTimerCallback,&context);
// 添加到runloop中
CFRunLoopAddTimer(runLoop,timer,kCFRunLoopCommonModes);
}
注释:上面是使用Core Foundation创建timer
配置基于端口的输入源
-(void)port
{
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 子线程中执行
dispatch_async(queue, ^{
// 添加观察者监听runloop状态变化
[self addObserver];
// 获取当前runloop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
// 添加port 因为在子线程中需要手动创建runloop,而且runloop没有任何输入源的情况下会马上退出,这里添加port
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 运行十秒后退出runloop
[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
});
}
2017-09-08 18:43:40.712 runloop应用[77177:42025885] 进入
2017-09-08 18:43:40.712 runloop应用[77177:42025885] 即将处理Timer事件
2017-09-08 18:43:40.712 runloop应用[77177:42025885] 即将处理Source事件
2017-09-08 18:43:40.712 runloop应用[77177:42025885] 即将休眠
2017-09-08 18:43:50.712 runloop应用[77177:42025885] 被唤醒
2017-09-08 18:43:50.712 runloop应用[77177:42025885] 退出RunLoop
通过打印结果可以看出,runloop在运行10秒后退出了。因为使用了runUntileDate,如果使用run就不会退出了。
下载完整demo地址