美文网首页
iOS runloop 原理讲解和常见问题分析

iOS runloop 原理讲解和常见问题分析

作者: 孙掌门 | 来源:发表于2019-12-20 15:43 被阅读0次

    iOS runloop 原理讲解

    runloop 是通过内部维护的事件循环来对事件消息进行管理的一个对象,runloop 其实就是一个对象

    事件循环,EventLoop

    1. 没有消息处理时候,休眠避免资源占用,从用户态转移到内核态
    2. 有消息处理时候,立刻被唤醒,从内核态到用户态转移

    什么是内核态用户态,内核态用户态

    main函数

    main 函数内部其实就是维护了一个 runloop , 我们的 runloop 通过维护一个事件循环来管理的,我们的有事件要做的时候,runloop 处于活跃状态,当没有的时候,runloop,会从用户态转移到内核态,当我们有事件做的时候,又会从内核态转移到我们的用户态,这就是main函数的实现机制。

    runloop 数据结构

    NSRunLoop 是对 CFRunLoop的封装

    
    1. CFRunLoop
    2. CFRunloopMode
    3. Source/Timer/Observer
    
    

    CFRunloop

    
    1. pthread > runloop 是和线程一一对应的
    2. currentMode>CFRunloopMode
    3. modes>NSMutableSet<CFRunLoopMode>
    4. commonModes>NSMutableSet<NSString *>
    5. commonModeItems>observer,timer,source
    
    

    CFRunloopMode:

    1. name > NSDefaultRunLoopMode
    2. sources0 > set
    3. sources1 > set
    4. observers > arr
    5. timers > arr
    
    

    CFRunLoopSource:

    1. source0,需要手动唤醒线程
    2. source1,具备唤醒线程的能力
    
    

    CFRunLoopObserver

    
    1. kCFRunLoopEntry,进入
    2. kCFRunLoopBeforeTimers,将要处理timer事件
    3. kCFRunLoopBeforeSources,将要处理source事件
    4. kCFRunLoopBeforeWaiting,将要休眠状态,将要从用户态切换到内核态
    5. kCFRunLoopAtferWaiting,将要唤醒,内核态切换到用户态切换后不久
    6. kCFRunLoopExit,退出
    
    

    runloop 和 线程是一一对应的,和mode是一对多,mode是事件也是一对多

    RUnLoop Mode:

    一个runloop可以有多个 mode , 一个mode有多个事件,source observer,timer事件,当我们有多个mode的时候,当其中的一个mode的某一个事件响应的时候,其余的mode不知道,互不影响,也就是说,mode1接受不到mode2的事件,不能处理其他mode的source,timer,observer。

    比如tableview的滑动,很典型的一个例子,timer的mode设置,tableview的滑动是否影响timer的响应,

    NSRunloopCommonModes 特性:commonMode 不是实际存在的一种mode,他是同步source,timer,observer到多个mode的一种技术解决方法。

    RunLoop 事件循环机制

    1. 即将进入runloop
    2. 将要处理 timer/ source0 事件
    3. 处理source0事件
    4. 处理source1事件,如果有,进入8
    5. 将要休眠,从用户态转移到内核态
    6. 休眠等待唤醒,可以唤醒runloop的事件,source1如点击屏幕,触发machPort,timer事件和外部的手动唤醒
    7. 线程刚被唤醒
    8. 处理唤醒时候收到的消息
    
    

    runloop 与 NSTimer 之间的关系

    像我之前说的,滑动tableView 的时候,我们的 NSTImer 还会生效吗?

    我们的tableView是运行在 KCFRunLoopDefaultMode 上面,而当我们滑动tableView 的时候,就切换到了,UITrackingRunLoopMode,上面说到,当我们把timer放到某一个mode上面之后,别的mode触发是不会生效的,所以我们创建timer默认是再default上面的,所以切换到track,timer是不知道的,那么怎么做呢?

    1. CFRunLoopAddTimer(runloop,timer,commonMode)
    
    

    我们来看下源码

    
    void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return;
        if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
        __CFRunLoopLock(rl);
        if (modeName == kCFRunLoopCommonModes) {
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    
    

    可以看到是先判断了if (modeName == kCFRunLoopCommonModes) 是否为 commonMode, 如果是出杨建一个集合,然后判断 _commonModeItems 是否为空接着会把runlooptimer添加到_commonModeItems这个集合当中,然后将runloop和timer封装成context,紧接着会对做 __CFRunLoopAddItemToCommonModes这个方法调用,

    static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
        CFStringRef modeName = (CFStringRef)value;
        CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
        CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
        if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
        } else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
        } else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
        CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
        }
    }
    
    

    这个方法的开始会把modename ,runloop 和 item 都取出来,然后判断下这个item的类型,是source,timer,observer,然后调用add方法,这时候的mode已经被加上的commonMode的标记了,这样就可以实现把多个timer同步到多个timer下面,其实原理就是他发现你是comonMode,然后打上标记,判断你是什么时间,然后再调用add方法,将timer添加到多个mode当中。

    所以我们如果把timer设置为commonMode之后,他就会帮我们把timer同步到defaultMode 和 trackMode,这样timer就不会停止了

    线程和 runLoop

    线程和runloop 是一一对应的,线程中的runloop默认是不创建的,我们需要手动创建

    那么我们怎样来保证子线程数据回来更新UI的时候,而不去打断tableView的滑动操作呢?我们可以把子线程回来更新主线程UI的操作,封装起来,放到主线程的runloopMode的defaultmode下面,这样我们tableView进入trackMode的时候,我们的defaultMode是不会影响的。

    runloop 常驻线程

    1. 为当前线程创建一个 runloop
    2. 向该runloop添加一个 source/port 事件来维持runloop的事件循环
    3. 启动该runloop
    
    

    runloop 是怎样做到没事件就休息,有事件就工作的?

    其实系统就是发生了一个用户态和内核态的一个相互转换,当我们调用CFRunLoopRun 的时候,系统通过调用mach_msg,来进行相互的转化

    相关文章

      网友评论

          本文标题:iOS runloop 原理讲解和常见问题分析

          本文链接:https://www.haomeiwen.com/subject/lkrinctx.html