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

2019-12-20  本文已影响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,来进行相互的转化

上一篇下一篇

猜你喜欢

热点阅读