Run Loop

本文详细介绍了Run Loop的概念,包括事件源、模式、观察者和定时器源。Run Loop是线程的事件循环,用于接收事件并分发给相应处理器。通过模式管理事件源,提高响应速度。同时,文章通过示例展示了如何添加观察者和使用定时器。还探讨了输入源的工作原理,以及如何启动和控制Run Loop的运行。Run Loop本质上是一个事件分发器,利用生产者消费者模型实现线程间通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念介绍

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: 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个成员:

  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时就调用

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


  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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值