Run Loop

来源:互联网 发布:python 登录界面 编辑:程序博客网 时间:2024/06/08 11:38

概念介绍

Run Loop 顾名思义,运行循环,又叫事件循环,俗称跑圈,实际上就是个循环。
它从事件源接收事件,然后分发给相应的事件处理器,如果没有要处理的事件,它使线程睡眠,从而节省CPU时间

本篇介绍 CFRunLoop, 因为NSRunLoop是CFRunLoop的封装,所以理解了CFRunLoop也就理解了NSRunLoop

运行循环的几个核心概念:

  1. CFRunLoopRef
  2. CFRunLoopSourceRef
  3. CFRunLoopTimerRef
  4. CFRunLoopMode
  5. CFRunLoopObserverRef

事件源

有两种事件源

  1. Input source,即 CFRunLoopSourceRef
  2. 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个成员:

  1. schedule 运行循环开始运行且运行在该输入源所在mode时,调用一次
  2. cancel 运行循环停止或输入源被移除时,调用一次
  3. 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, performinfo成员设置为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


  1. CFRunLoopRun 默认运行在 kCFRunLoopDefaultMode 模式,且循环不会退出,除非通过CFRunLoopStop来停止循环,或者通过或CFRunLoopRemoveSource移除运行循环中的所有输入源和通过CFRunLoopRemoveTimer移除所有定时器,运行循环才会退出,并返回。
  2. 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
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 出租房墙面太脏怎么办 苹果6镜头模糊了怎么办 6s前摄像头进灰怎么办 手机镜头进灰了怎么办 6s摄像头进灰了怎么办 7plus摄像头进灰怎么办 苹果喇叭进灰了怎么办 苹果7摄像头进灰怎么办 锁眼里胶水堵了 怎么办 锁眼被牙签堵了怎么办 锁孔被胶水堵了怎么办 快手上不了同城怎么办 昌珉入伍宋茜怎么办 嗓子里卡了鱼刺怎么办 在餐厅吃到虫子怎么办 在餐厅吃出虫子怎么办 孕妇被虫子咬了怎么办 吃外卖吃到虫子怎么办 杯子盖拧错位了怎么办 身边有吸毒的人怎么办 如果牛难产了怎么办要 牛难产拉不出来怎么办 老公发现老婆有外遇怎么办 睡眠不好半夜老是醒怎么办 拔完智齿肿了怎么办 拔牙后咽口水疼怎么办 吃了脏东西拉肚子怎么办 微信遇到仙人跳怎么办 牙有裂痕疼应该怎么办 胸罩在学校掉了怎么办 锁屏密码忘记了怎么办 中汇支付不到账怎么办 痘痘毁容烂脸怎么办 我的手机掉了怎么办 公司不给开工资怎么办 我有卵巢老化怎么办呢 老师骂了我,我该怎么办 我的牙齿很难看怎么办 门牙摔了个缺怎么办 鱼身上鱼鳞烂了怎么办 鱼身上发白烂了怎么办