Run Loop
来源:互联网 发布:python 登录界面 编辑:程序博客网 时间:2024/06/08 11:38
概念介绍
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: kCFRunLoopEntry2017-10-30 21:18:14.940147+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeTimers2017-10-30 21:18:14.941047+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeSources2017-10-30 21:18:14.941081+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeWaiting2017-10-30 21:18:14.941099+0800 runloop[88225:2406625] activity: kCFRunLoopAfterWaiting2017-10-30 21:18:14.941116+0800 runloop[88225:2406625] ------------ timer --------------2017-10-30 21:18:14.941158+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeTimers2017-10-30 21:18:14.941170+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeSources2017-10-30 21:18:14.941179+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeWaiting2017-10-30 21:18:19.944040+0800 runloop[88225:2406625] activity: kCFRunLoopAfterWaiting2017-10-30 21:18:19.944102+0800 runloop[88225:2406625] ------------ timer --------------2017-10-30 21:18:19.944145+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeTimers2017-10-30 21:18:19.944161+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeSources2017-10-30 21:18:19.944172+0800 runloop[88225:2406625] activity: kCFRunLoopBeforeWaiting2017-10-30 21:18:24.944739+0800 runloop[88225:2406625] activity: kCFRunLoopAfterWaiting2017-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时就调用
CFRunLoopSourceSignalCFRunLoopWakeUp
通知运行循环任务的到来,运行循环被唤醒,随后运行循环调用输入源的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] ==
启动运行循环
CFRunLoopRunCFRunLoopRunInMode
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
- Run Loop
- Run Loop
- Run Loop
- Run Loop
- Run Loop
- Run Loop
- run loop
- Run Loop
- Run Loop
- run loop
- Run Loop
- Run Loop
- Run Loop
- run loop
- run loop
- run loop
- RUN Loop
- run loop
- [NOIP2017模拟]星星
- 项目开发中git分支规范
- java类加载模式与web容器的类加载模式
- 菲波那切数列与生成器
- c++基础之多重继承
- Run Loop
- 【实战】qq账户和密码的发送器
- 生活小记36
- 括号配对问题//正在奋斗的弱弱的程序员
- CSS 经典导航
- JZOJ 5436. 【NOIP2017提高A组集训10.30】Group
- poj1753Flip Game之 广搜解法+暴搜解法
- 聚类之DBSCAN学习
- 国际化