AG亚游集团 >iOS开发

iOS 浅谈 Runloop

2019-01-07 11:07 编辑: 米米狗 分类:AG亚游集团iOS开发 来源:swordjooy

RunLoop 是什么

强烈推荐 ibireme 大神的文章深入理解RunLoop

Runloop源码地址

关于 Runloop ,尽管早就知道它的本质实现是一个循环,但笔者还是一直很困惑它的作用是什么 ,不过最近整理相关知识总算是理解了。

代码的执行逻辑是自上而下的,如果没有 Runloop ,代码执行完毕后,程序就退出了,对应到实际场景就是 APP 一打开立马就退出了。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"程序执行中...");
    }
    return 0;
}
/ log
程序执行中...
Program ended with exit code: 0

例如上面的代码,代码执行完毕后,main 函数返回,然后程序退出。

为什么工作中,好像没有编写 Runloop 相关的代码,程序还是能够稳定持续运行呢?

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nilNSStringFromClass([AppDelegate class]));
    }
}

这是因为程序自动帮我们在 UIApplicationMain… 中做了这个事情。

下面来看看 Runloop 的简化的伪代码,主要来自 sunnyxx  大神的一次视频分享:

function loop({
    do {
        有事干了 = 我睡觉了没事别找我();
        if (搬砖) {
            搬砖();
        } else if (吃饭) {
            吃饭();
        }
    } while (活着)
}

这个伪代码看着还是有一点抽象,需要了解的一个知识点是线程和 RunLoop 之间是一一对应的,这里的睡觉了可以理解为线程休眠 [NSThread sleepUntilDate:...]],也就是说当应用没有任何事件触发时,就会停在睡觉那行代码不执行,这样就节约了 CPU 的运算资源,提高程序性能,直到有事件唤醒应用为止。例如上面的搬砖事件,吃饭事件。处理完后,又会进入睡觉状态直到下次唤醒,反复循环,这样就保证了程序能随时处理各种事件并能够稳定运行。

实际上触摸事件、屏幕 UI 刷新、延迟回调等等都是 Runloop 实现的。

Runloop 的结构

先来看看 Runloop 的结构源码:

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;     
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    / ...
};

这里包含一个线程的成员变量 _pthread,可以看出 Runloop 确实和线程是息息相关的。还能看到 Runloop 拥有很多关于 Model 的成员变量,再来看看 Model 的结构:

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    / ...
};

先不管这些东西是干什么的,至少我们现在能够得出如下图所示的理解:

image

一个 Runloop 中包含若干个 Model ,每个 Mode 又包含若干个 Source/Timer/Observer。

Runloop 的 Model

Model 代表 Runloop 的运行模式,Runloop 每次只能指定一个 Model 作为 _currentMode ,如果需要切换 Mode,只能退出当前 Loop,再重新选择一个 Mode 进入。主线程的 Runloop 这里有两个预置的模式 ,并且这也是系统公开的两个 Model:

  • kCFRunLoopDefaultMode:APP 的普通状态,通常主线程是在这个Mode下运行,已被标记为 Common。

  • UITrackingRunLoopMode:App 追踪触摸 ScrollView 滑动时的状态,保证界面滑动时不受其他 Mode影响,已被标记为 Common。

注意 Runloop 的结构中有一个 _commonModes 。这里是因为一个 Mode 可以将自己标记为 Common (通过将其 ModeName 添加到 RunLoop 的 commonModes 中 ),标记为 Common 的 Model 都可以处理事件,可以理解为变相的实现了多个 Model 同时运行。同时系统也提供了一个操作 Common 标记的字符串->kCFRunLoopCommonModes。如果我们想要上面两种模式下都能处理事件,就可以使用这个字符串。

Model 中的 Item

Source/Timer/Observer 被统称为 mode item,不同 Model 的 Source0/Source1/Timer/Observer 被分隔开来,互不影响,如果 Mode 里没有任何Source0/Source1/Timer/Observer,RunLoop 会立马退出。

Source

Source 是事件产生的的地方,它对应的类为 CFRunLoopSourceRef。Source 有两个版本:Source0 和 Source1。

  • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。

  • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。例如屏幕触摸、锁屏和摇晃等。

Timer

Timer 对应的类是 CFRunLoopTimerRef,它其实就是 NSTimer,当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

Observer

Observer 对应的类是 CFRunLoopObserverRef,当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

typedef CF_OPTIONS(CFOptionFlagsCFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), / 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), / 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), / 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), / 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), / 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), / 即将退出Loop
};

Runloop 的内部逻辑

打开开头的 Runloop 的源码,面对众多代码,让人毫无头绪,但是前文中已经讲到,屏幕的触摸事件是 Runloop 来处理的。于是打个断点,来查看程序的函数调用栈:

image

从图中能看到,Runloop 是从 11 开始的,于是从源码中搜索 CFRunLoopRunSpecific 函数,这里只探究内部主要逻辑,其他细节不看,下面是精简后的函数:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    / 根据 modeName 获取currentMode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    / 设置 Runloop 的 Model
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    / 通知 Observers: 即将进入 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    / 进入 runloop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    / 通知 Observers: RunLoop 即将退出
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}

然后再进入 __CFRunLoopRun(...) 函数查看内部精简后的主要逻辑源码:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        / 通知 Observers: 即将处理 Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        / 通知 Observers: 即将处理 Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        / 处理 Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        / 处理 Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            / 处理 Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        / 判断有无 Sources1
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            / 跳转到 handle_msg 处理 Sources1soso
            goto handle_msg;
        }
        / 通知 Observers: 即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        / 开始休眠
        __CFRunLoopSetSleeping(rl);

        / 等待消息唤醒当前线程
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        / 结束休眠
        __CFRunLoopUnsetSleeping(rl);
        / 通知 Observers: 结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    / 处理
    handle_msg:;
        / 被 timer 唤醒
        if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            / 处理 timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        }
        / 被 gcd 唤醒
        else if (livePort == dispatchPort) {
            / 处理 gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        / 被source1唤醒
        } else {
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }

        / 处理 Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        / 设置返回值
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
    } while (0 == retVal);
    return retVal;
}

可以看到 Runloop 内部确实是一个循环,并且,唤醒 RunLoop 的方式有 mach port 、Timer 和 dispatch

。笔者最初在疑惑一个问题,上面的函数调用栈是一个点击屏幕后的响应事件,可以看出这里是 sources0 ,明明是一个触摸事件为什么不是 sources1 呢,笔者猜测 sources1 这里唤醒了 Runloop ,因为 sources0 是无法唤醒 runloop 的,然后再在 sources0 的回调中处理的点击事件。

RunLoop 中的 mach port

这里由于目前笔者水平有限,只能够理解到 mach port 是一个可以控制硬件和接受硬件反馈的一个系统,然后可以通过它将来自硬件的操作转化成熟知的 UIEvent 事件等等。

总结

这篇文章主要讲解了 Runloop 到底是一个什么东西,当然 Runloop 的知识不仅仅只有这篇文章这点。例如实际用处中的线程保活(AFNetworking 2.x 版本中),滑动时 Timer 怎么不被停止,自动释放池的实现等等都用到了 Runloop 。

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
上一篇:[App探索]JSBox中幽灵触发器的实现原理探索

相关资讯

我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部
施密特:比埃拉状态不错有望出场 不担心中后场防守 深度 | 女子马拉松的昨天今天与明天 浪潮孙丕恕:推动人工智能与工业互联网的融合发展 马德里赛卡萨金娜三盘淘汰穆古 进八强将战科娃 特朗普称美国经济增速能达8%-9%,专家认为3%都够呛 美联储主席遭逼问:有无对富国银行制裁令执行计划? 杨晶被撤销国务委员国务院秘书长职务 量子袜?量子鞋垫?中科院院士潘建伟:假的! 奇牛国际:美国4月PPI不及预期 美指冲高回落 消息称投资者赴宜信总部维权 回应:严守合规底线 美永久缩减驻古巴大使馆人员 古巴外交部官员回应 云南通报游船侧翻事故:驾驶员为增刺激感连续转弯
王宝山:无谓失误毁掉好局 创造很多机会但功亏一篑 韩国审了三位前总统后 轮到李明博时出现扎心一幕 津媒:一方对足协杯执念让人不解 对中超没好作用 3名儿童掉进冰窟窿 “旱鸭子”小伙下水救出 正路多达9场 足彩18035期任九喷34475注501… 4月17日四大证券报头版头条内容精华摘要 银河期货:受天气影响 大豆期价低位反弹 阿不都:亚运力争好成绩 周琦小丁能决定胜负 沙特首次允许女性参军:须满足至少12项要求 湖人9人上双强势三连胜 球哥回勇送老鹰四连败 美学者:要让霍金具备获诺奖条件或许要等数十亿年 学者:开采比特币会“吃掉” 地球上0.5%的电力
一波未平一波又起 台“友邦”海地被曝或与台断交 18岁的詹科早被NBA智多星看穿 他也看走眼一人 宁夏检察院副检察长田云鹏任江西检察院党组书记 这家中国手机企业打败三星称雄非洲 今要A股上市 中国今年第8次巡航钓鱼岛12海里 遭日船跟踪干扰 北京市长陈吉宁都担任过哪些小组组长? 贸易战预警信号初现 德国一季度经济增速下滑 印度一辆公共汽车失控坠入峡谷 已致33死 侮辱南京大屠杀遇难者的男子道歉:我纯属有病 投资两万的小型加工厂 三缺一棋牌游戏平台 适合白手起家的项目 一万元猪舍建设图片 AG亚游集团