IOS开发入门之iOS 开发runLoop 机制详解
白羽 2018-11-26 来源 :网络 阅读 805 评论 0

摘要:本文将带你了解IOS开发入门iOS 开发runLoop 机制详解,希望本文对大家学IOS有所帮助。

    本文将带你了解IOS开发入门iOS 开发runLoop 机制详解,希望本文对大家学IOS有所帮助。



        


NSRunLoop机制

对于iOS开发,runLoop机制还是很有必要了解一下的,最近在做一个广告图的功能正好需要了解下runtime机制问题,在查看了官方文档API以及论坛贴吧博客各位大牛的文章后,整理下关于我自己的理解和总结.

首先说介绍下NSRunLoop

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

NSRunLoop 与线程的关系

其实runLoop就是一个do … while()函数,每个runLoop对应一个线程他们是一一对应的关系,关系保存在一个全局的Dictionary里边,线程刚创建时没有RunLoop,如果不主动获取,是不会有的,RunLoop的创建发生在第一次获取时,RunLoop的销毁发生在线程结束,只能在一个线程的内部获取它的RunLoop(主线程除外)主线程默认有个RunLoop.

Thread包含一个CFRunLoop,一个CFRunLoop包含一种CFRunLoopMode,mode包含CFRunLoopSource,CFRunLoopTimer和CFRunLoopObserver。

RunLoop只能运行在一种mode下,如果要换mode当前的loop也需要停下重启成新的。利用这个机制,ScrollView过程中NSDefaultRunLoopMode的mode会切换UITrackingRunLoopMode来保证ScrollView的流畅滑动不受只能在NSDefaultRunLoopMode时处理的事件影响滑动。同时mode还是可定制的。

NSDefaultRunLoopMode:默认,空闲状态
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes:Mode集合 Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes来解决

 


   

//然后再添加到NSRunLoopCommonModes里

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0

     target:self

     selector:@selector(timerTick:)

     userInfo:nil

     repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

   



   

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef

static CFMutableDictionaryRef loopsDic;

/// 访问 loopsDic 时的锁

static CFSpinLock_t loopsLock;

 

/// 获取一个 pthread 对应的 RunLoop。

CFRunLoopRef _CFRunLoopGet(pthread_t thread) {

    OSSpinLockLock(&loopsLock);

 

    if (!loopsDic) {

        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。

        loopsDic = CFDictionaryCreateMutable();

        CFRunLoopRef mainLoop = _CFRunLoopCreate();

        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);

    }

 

    /// 直接从 Dictionary 里获取。

    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

 

    if (!loop) {

        /// 取不到时,创建一个

        loop = _CFRunLoopCreate();

        CFDictionarySetValue(loopsDic, thread, loop);

        /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。

        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);

    }

 

    OSSpinLockUnLock(&loopsLock);

    return loop;

}

 

CFRunLoopRef CFRunLoopGetMain() {

    return _CFRunLoopGet(pthread_main_thread_np());

}

 

CFRunLoopRef CFRunLoopGetCurrent() {

    return _CFRunLoopGet(pthread_self());

}

   

介绍完线程和RunLoop的关系后,主要看下RunLoop和RunLoopModel,
RunLooprunLoop 在CoreFoundation对外一共有五大类最为对外的接口:

RunLoop

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

1.其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装


一个 RunLoZ喎?"/kf/ware/vc/" target="_blank" class="keylink">vcCCw/LqsyPS4ybj2IE1vZGWjrMO/uPYgTW9kZSDT1rD8uqzI9LjJuPYgU291cmNlL1RpbWVyL09ic2VydmVyoaPDv7TOtffTwyBSdW5Mb29wILXE1ve6r8r9yrGjrNa7xNzWuLaoxuTW0NK7uPYgTW9kZaOs1eK49k1vZGWxu7PG1/cgQ3VycmVudE1vZGWho8jnufvQ6NKqx9C7uyBNb2Rlo6zWu8Tczcuz9iBMb29wo6zU2dbY0MLWuLao0ru49iBNb2RlIL34yOuho9Xi0fnX9tb30qrKx86qwcu31rj0v6qyu82s1+m1xCBTb3VyY2UvVGltZXIvT2JzZXJ2ZXKjrMjDxuS7pbK707DP7KGjPC9wPg0KPGg0IGlkPQ=="2-cfrunloopsourceref">2. CFRunLoopSourceRef

是事件产生的地方,Source包含了两个部分:Source0 和 Source1。
source0:处理如UIEvent,CFSocket这样的事件
source1:Mach port驱动,CFMachport,CFMessagePort

Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

Source1 包含了一个 mach_port 和一个回调(函数指针),被用于和通过内核和其他 mach_port 相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

3. CFRunLoopTimerRef

NSTimer是对RunLoopTimer的封装
是基于时间的触发器,它和 NSTimer 是toll-free bridge 的。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调

4. CFRunLoopObserverRef

Cocoa框架中很多机制比如CAAnimation等都是由RunLoopObserver触发的。observer到当前状态的变化进行通知

观察者,每个 Ovserver 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

 


   

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop

    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer

    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source

    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠

    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒

    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop

};

   



 

RunLoopModel

CFRunLoopMode 和 CFRunLoop 的结构大致如下:

 


   

struct __CFRunLoopMode {

    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"

    CFMutableSetRef _sources0;    // Set 

    CFMutableSetRef _sources1;    // Set 

    CFMutableArrayRef _observers; // Array 

    CFMutableArrayRef _timers;    // Array 

    ...

};

 

struct __CFRunLoop {

    CFMutableSetRef _commonModes;     // Set 

    CFMutableSetRef _commonModeItems; // Set 

    CFRunLoopModeRef _currentMode;    // Current Runloop Mode

    CFMutableSetRef _modes;           // Set 

    ...

};

   


 

这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。RunLoop 会自动将 _commonModeItems 加入到具有 “Common” 标记的所有Mode里。

应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。

RunLoop底层实现

从上面代码可以看到,RunLoop的核心是基于 mach port 的,其进入休眠时调用的函数是 mach_msg()。为了解释这几个概念,下面稍微介绍一下OSX/iOS的系统架构。

苹果官方将整个系统大致划分为上述4个层次:

应用层包括用户能接触到的图形应用,例如 Spotlight、Aqua、SpringBoard等。

应用框架层即开发人员接触到的 Cocoa 等框架。

核心框架层包括各种核心框架、OpenGL 等内容。

Darwin 即操作系统的核心,包括系统内核、驱动、Shell 等内容,这一层是开源的,其所有源码都可以在opensource.apple.com里找到。

 

 


   

CFRunLoop {

    current mode = kCFRunLoopDefaultMode

    common modes = {

        UITrackingRunLoopMode

        kCFRunLoopDefaultMode

    }

 

    common mode items = {

 

        // source0 (manual)

        CFRunLoopSource {order =-1, {

            callout = _UIApplicationHandleEventQueue}}

        CFRunLoopSource {order =-1, {

            callout = PurpleEventSignalCallback }}

        CFRunLoopSource {order = 0, {

            callout = FBSSerialQueueRunLoopSourceHandler}}

 

        // source1 (mach port)

        CFRunLoopSource {order = 0,  {port = 17923}}

        CFRunLoopSource {order = 0,  {port = 12039}}

        CFRunLoopSource {order = 0,  {port = 16647}}

        CFRunLoopSource {order =-1, {

            callout = PurpleEventCallback}}

        CFRunLoopSource {order = 0, {port = 2407,

            callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}

        CFRunLoopSource {order = 0, {port = 1c03,

            callout = __IOHIDEventSystemClientAvailabilityCallback}}

        CFRunLoopSource {order = 0, {port = 1b03,

            callout = __IOHIDEventSystemClientQueueCallback}}

        CFRunLoopSource {order = 1, {port = 1903,

            callout = __IOMIGMachPortPortCallback}}

 

        // Ovserver

        CFRunLoopObserve    

   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之IOS频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程