概念介绍
Run Loop 顾名思义,运行循环,又叫事件循环,俗称跑圈,实际上就是个循环。
它从事件源接收事件,然后分发给相应的事件处理器,如果没有要处理的事件,它使线程睡眠,从而节省CPU时间
本篇介绍 CFRunLoop
, 因为NSRunLoop是CFRunLoop的封装,所以理解了CFRunLoop
也就理解了NSRunLoop
运行循环的几个核心概念:
- CFRunLoopRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopMode
- CFRunLoopObserverRef
事件源
有两种事件源
- Input source,即
CFRunLoopSourceRef
- Timer source,即
CFRunLoopTimerRef
这两种源后面以具体例子解释
模式
模式,是用来给事件源分类的,比如我有十个事件源,我想前3个事件源放到一个数组里,中间5个放到一个数组里,最后2个放到一个数组里,那么我给三个数组编个号,分别叫模式1,模式2,模式3。当运行循环工作在模式1的时候,它只从第一个数组的三个源里接收事件并分发给相应的事件处理器。还有一种称为common模式的,它类似C语言里位运算的位或,比如:common_modes = mode1 | mode2
,那么当运行循环工作在common模式时,它会从第一个数组和第二个数组的事件源里接收事件。
把事件源按不同模式分组是为了改善响应时间,把需要高实时的事件源放到单独的模式数组里,并让运行循环工作在这个模式下,而忽略其他事件源,从而提高实时性。
观察者
观察者是用来观察Run Loop自身的一些活动阶段的,有一下几种活动阶段:
kCFRunLoopEntry // 进入循环
kCFRunLoopBeforeTimers // 马上处理定时器事件
kCFRunLoopBeforeSources // 马上处理输入源事件
kCFRunLoopBeforeWaiting // 马上休眠
kCFRunLoopAfterWaiting // 醒了
kCFRunLoopExit // 退出循环
观察者示例:
因为至少需要向run loop里添加一个事件源,才能启动run loop,所以这里除了注册观察者之外,添加了一个定时器源
static void timerCallBack(CFRunLoopTimerRef timer, void *info)
{
NSLog(@"------------ timer --------------");
}
static void observerCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"activity: kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"activity: kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"activity: kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"activity: kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"activity: kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"activity: kCFRunLoopExit");
break;
default:
break;
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSThread detachNewThreadWithBlock:^{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopObserverContext ctx = {
.version = 0,
.info = NULL,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
};
// 创建观察者
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observerCallBack, &ctx);
// 注册观察者
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopDefaultMode);
CFRunLoopTimerContext context = {
.version = 0,
.info = NULL,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
};
// 创建定时器源
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, 0, 5, 0, 0, &timerCallBack, &context);
// 添加定时器源到运行循环
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
// 启动运行循环
CFRunLoopRun();
}];
}
运行结果:
2017-10-30 21:18:14.939844+0800 runloop[88225:2406625] activity: kCFRunLoopEntry
2017-10-30 21:18:14.940147+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeTimers
2017-10-30 21:18:14.941047+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeSources
2017-10-30 21:18:14.941081+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeWaiting
2017-10-30 21:18:14.941099+0800 runloop[88225:2406625] activity: kCFRunLoopAfterWaiting
2017-10-30 21:18:14.941116+0800 runloop[88225:2406625] ------------ timer --------------
2017-10-30 21:18:14.941158+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeTimers
2017-10-30 21:18:14.941170+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeSources
2017-10-30 21:18:14.941179+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeWaiting
2017-10-30 21:18:19.944040+0800 runloop[88225:2406625] activity: kCFRunLoopAfterWaiting
2017-10-30 21:18:19.944102+0800 runloop[88225:2406625] ------------ timer --------------
2017-10-30 21:18:19.944145+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeTimers
2017-10-30 21:18:19.944161+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeSources
2017-10-30 21:18:19.944172+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeWaiting
2017-10-30 21:18:24.944739+0800 runloop[88225:2406625] activity: kCFRunLoopAfterWaiting
2017-10-30 21:18:24.944817+0800 runloop[88225:2406625] ------------ timer --------------
可以看到观察者的回调函数打印出了运行循环的各个活动阶段
定时器的回调函数每隔5秒调用一次
并且总是在kCFRunLoopAfterWaiting之后打印timer,说明是定时器事件唤醒了线程
定时器源
我们通过用CFRunloop来实现NSTimer,来学习定时器源的使用
代码用C++实现:
#ifndef Timer_h
#define Timer_h
#include <CoreFoundation/CFRunLoop.h>
#include <functional>
#include <memory>
#include <assert.h>
class Timer final {
public:
static std::unique_ptr<Timer> scheduledTimer(CFTimeInterval interval, const std::function<void(const Timer &)> &func)
{
auto timer = std::make_unique<Timer>(interval, interval, func);
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
timer->addToRunLoop(runLoop, kCFRunLoopCommonModes);
return timer;
}
public:
~Timer()
{
invalidate();
CFRelease(_timerRef);
}
Timer(CFAbsoluteTime fireDate, CFTimeInterval interval, const std::function<void(const Timer &)> &func)
: _func(func)
{
CFRunLoopTimerContext context = {
.version = 0,
.info = this, // 在回调函数中需要获取this
.retain = nullptr,
.release = nullptr,
.copyDescription = nullptr,
};
_timerRef = CFRunLoopTimerCreate(NULL, fireDate, interval, 0, 0, &timerCallBack, &context);
}
Timer(const Timer &) = delete;
Timer(Timer &&) = delete;
Timer &operator=(const Timer &) = delete;
Timer &operator=(Timer &&) = delete;
void addToRunLoop(CFRunLoopRef runLoop, CFRunLoopMode mode)
{
CFRunLoopAddTimer(runLoop, _timerRef, mode);
}
void invalidate()
{
if (isValid())
CFRunLoopTimerInvalidate(_timerRef);
}
bool isValid() const
{
return CFRunLoopTimerIsValid(_timerRef);
}
private:
static void timerCallBack(CFRunLoopTimerRef, void *info)
{
Timer *timer = static_cast<Timer *>(info);
if (timer->_func)
timer->_func(*timer);
}
private:
CFRunLoopTimerRef _timerRef = nullptr;
std::function<void(const Timer &)> _func;
};
#endif /* Timer_h */
测试
_timer = Timer::scheduledTimer(2, [](const Timer &timer) {
NSLog(@"timer");
});
输入源
关键类型CFRunLoopSourceContext
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
关键的3个成员:
- schedule 运行循环开始运行且运行在该输入源所在mode时,调用一次
- cancel 运行循环停止或输入源被移除时,调用一次
- perform 输入源有事件产生,运行循环通过调用该函数来分发事件
下面以具体代码说明输入源的使用
定义一个Worker类来封装 输入源,任务队列,事件处理器
#ifndef Worker_h
#define Worker_h
#include <iostream>
#include <queue>
#include <mutex>
#include <CoreFoundation/CFRunLoop.h>
class Worker final {
public:
typedef std::function<void()> Task;
public:
~Worker()
{
CFRelease(_sourceRef);
}
Worker()
{
CFRunLoopSourceContext srcCtx = {
.version = 0,
.info = this,
.retain = nullptr,
.release = nullptr,
.copyDescription = nullptr,
.equal = nullptr,
.hash = nullptr,
.schedule = schedule,
.cancel = cancel,
.perform = perform,
};
_sourceRef = CFRunLoopSourceCreate(nullptr, 0, &srcCtx);
}
Worker(const Worker &) = delete;
Worker(Worker &&) = delete;
Worker &operator=(const Worker &) = delete;
Worker &operator=(Worker &&) = delete;
void addTask(const Task &task)
{
std::lock_guard<std::mutex> guard(_mutex);
_taskQueue.push(task);
CFRunLoopSourceSignal(_sourceRef);
if (CFRunLoopIsWaiting(_runLoopRef))
CFRunLoopWakeUp(_runLoopRef);
}
// 在子线程调用
void run()
{
CFRunLoopAddSource(CFRunLoopGetCurrent(), _sourceRef, kCFRunLoopDefaultMode);
CFRunLoopRun();
}
void stop()
{
CFRunLoopStop(_runLoopRef);
}
private:
Task nextTask()
{
std::lock_guard<std::mutex> guard(_mutex);
if (_taskQueue.size()) {
auto task = _taskQueue.front();
_taskQueue.pop();
return task;
}
return Task();
}
private:
static void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode)
{
Worker *worker = static_cast<Worker *>(info);
worker->_runLoopRef = rl;
}
static void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode)
{
std::cout << "cancel...." << std::endl;
}
static void perform(void *info)
{
Worker *worker = static_cast<Worker *>(info);
auto task = worker->nextTask();
if (task)
task();
}
private:
std::mutex _mutex;
std::queue<Task> _taskQueue;
CFRunLoopSourceRef _sourceRef = nullptr;
CFRunLoopRef _runLoopRef = nullptr;
};
#endif /* Worker_h */
构造函数创建了一个输入源,设置了三个回调函数schedule
, cancel
, perform
,info
成员设置为this,在回调函数中需要获取this指针
Worker管理一个任务队列_taskQueue
, 每次调用addTask时就调用
CFRunLoopSourceSignal
CFRunLoopWakeUp
通知运行循环任务的到来,运行循环被唤醒,随后运行循环调用输入源的perform事件处理器
perform事件处理器从任务队列中取出任务,执行之
在子线程中调用run来启动子线程的运行循环
测试代码:
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
{
Worker _worker;
}
- (IBAction)buttonClicked:(id)sender
{
_worker.addTask([] {
NSLog(@"==");
});
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSThread detachNewThreadWithBlock:^{
_worker.run(); // 在子线程中调用run
}];
}
@end
我们是在主线程中创建的Worker,在主线程中添加任务,在子线程中调用run
通过点击按钮模拟添加任务,随后子线程的运行循环就会取出任务并执行。
点击按钮,可以看到控制台输出:
2017-11-01 14:03:09.160570+0800 runloop[89992:2953840] ==
2017-11-01 14:03:10.208340+0800 runloop[89992:2953840] ==
2017-11-01 14:03:10.656398+0800 runloop[89992:2953840] ==
启动运行循环
CFRunLoopRun
CFRunLoopRunInMode
CFRunLoopRun
默认运行在 kCFRunLoopDefaultMode 模式,且循环不会退出,除非通过CFRunLoopStop来停止循环,或者通过或CFRunLoopRemoveSource移除运行循环中的所有输入源和通过CFRunLoopRemoveTimer移除所有定时器,运行循环才会退出,并返回。CFRunLoopRunInMode
可以指定运行的模式,可以设置循环运行时长,这个函数有几种可能会返回:
kCFRunLoopRunFinished // 运行循环里没有输入源也没有定时器
kCFRunLoopRunStopped // CFRunLoopStop使用来停止运行循环
kCFRunLoopRunTimedOut // 指定运行时长到了
kCFRunLoopRunHandledSource 当参数returnAfterSourceHandled为true时,处理一个输入源或定时器源就返回
使用CFRunLoopRunInMode来启动运行循环,一般需要自己再套一个while循环,来阻止线程退出
[NSThread detachNewThreadWithBlock:^{
// 添加输入源
// ...
while (true) {
CFRunLoopRunResult result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, false);
if (result == kCFRunLoopRunStopped || result == kCFRunLoopRunFinished)
break;
}
}];
CFRunLoopRunInMode 可以提前返回从而允许我们在下一次迭代的时候切换模式,猜测iOS中的UIScrollView就是通过判断是否在滚动时切换到UITrackingRunLoopMode,从而忽略其它模式的输入源,和定时器源
总结
运行循环,本质上就是个循环。它本身的功能有限,主要功能就是:
当有事件产生时,调用事件处理器
当没有事件产生时,使线程睡眠
它既不生产事件也不处理事件,它只是事件的分发者
运行循环使用的线程间通信模型是生产者消费者模型
------------- ------------ ------------
| 事件生产者 | -> | 事件分发者 | -> | 事件消费者 |
------------- ------------ ------------
例如:
timer -> run loop -> handler
task -> run loop -> run task