美文网首页
RunLoop篇-事件循环的实现机制

RunLoop篇-事件循环的实现机制

作者: 亲爱的大倩倩 | 来源:发表于2019-07-18 18:03 被阅读0次

在Runloop启动后,首先会发出一个通知.告知观察者即将启动,之后将要处理Timer / Source0事件,然后进入正式的source0处理,如果有source1要处理,会通过一条goto语句实现,进行代码逻辑跳转,去处理唤醒时收到的消息,如果没有source1处理,此时线程即将进入休眠,同时也会发送通知给Observer,然后发生从用户态到内核态的切换,然后线程正式进入休眠,等待唤醒
唤醒线程或者RunLoop的条件有三个

  1. 通过Source1进行相关事件的唤醒
  2. Timer事件的回调到了
  3. 外部手动唤醒

当一个处于休眠状态的RunLoop,可以通过上面三个方式唤醒它

当线程被唤醒后,也会发送通知告诉观察者
然后处理唤醒时收到的消息
然后又会回到将要处理Timer以及Source0事件的代码逻辑
然后通知observer,之后顺次向下执行
线程再次进入休眠,等待唤醒

程序从点击图标到程序启动,到程序被杀死这个过程中,系统是怎样实现的?
当调用Main函数之后,会调用UIApplicationMain函数,在内部启动主线程的RunLoop,经过一系列的处理,最终主线程的RunLoop处于休眠状态,之后如果点击了屏幕,会转成Source1, 就会把主线程唤醒,进行后续处理,当我们把线程杀死的时候,会发生RunLoop的退出通知,退出之后,线程就被销毁掉了


用户态和核心态是怎么切换的
main函数经过一系列处理后,系统内部会调用mach_msg函数,发生系统调用,经过这个调用,当前用户线程就把控制权转交给核心态
mach_mag在一定条件下会返回给调用方,触发返回的逻辑就是唤醒线程的逻辑
1,通过Source1进行相关事件的唤醒
2,Timer事件的回调到了
3,外部手动唤醒
然后就从核心态回到用户态的切换,当前APP的主线程循环就会被唤醒

RunLoop与NSTimer

滑动tableView时,定时器还会生效吗?
当前tableView正常情况下,是运行在kCFRunLoopDefaultMode模式下的,当我们对tableView进行滑动的时候,会发生mode切换,切换到UITrackingRunLoopMode模式下,

之前说,当我们把source / Timer / Observer添加到某个mode上面之后,如果当前Runloop是运行在另一个Mode上面的话,对应的这些Timer和Source和Observer是没有办法进行后续处理回调的,因为Mode有多个解决的问题就是Timer和Source和Observer的隔离,这种隔离产生的问题就是创建的Timer默认情况下添加到kCFRunLoopDefaultMode模式下,切换到UITrackingRunLoopMode后,定时器就不会再生效了
解决的办法是:

  1. void CFRunLoopAddTimer(runLoop, timer, commonMode)
    通过这个函数,将NSTimer添加到当前RunLoop的commonMode中, commonMode不是一个世纪的mode,只是把一些mode打上common的标记,然后可以把某个事件源比如说是Timer同步到多个mode中
//参数: runloop,准备要添加的参数,模式名称
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);
    //如果当前模式名称是CommonModes
    if (modeName == kCFRunLoopCommonModes) {
        //首先会提取RunLoop的commonModes
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        //同时判断runloop对应的commonItems是否为空,若为空,会重新创建集合
    if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
        //把timer添加到commonModeItems数组中
    CFSetAddValue(rl->_commonModeItems, rlt);
    if (NULL != set) {
        //再把timer和Runloop封装成一个context,
        CFTypeRef context[2] = {rl, rlt};
        //之后对集合中的每一个元素,都调用__CFRunLoopAddItemToCommonModes函数(对象元素,context)
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    //取当前mode的名字,然后吧Runloop和Item取出来
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    //根据当前item类型判断,来决定调用CFRunLoopAddSource还是CFRunLoopAddObserver还是CFRunLoopAddTimer,并不是循环调用,因为传进来的modeName 参数已经从commonMode变成被打上了标记的具体实际的mode
    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);
    }
}

如果CFRunLoopAddTimer传递进来的是个具体的mode,那么在后续就会把timer最终添加到对应的RunLoop Mode下面的timers数组中,就可以实现:通过把多个mode中都添加同一个Timer
因为调用CFSetApplyFunction函数,实际上是对于集合当中每个元素都调用了CFRunLoopAddItemToCommonMode函数,就可以实现把多个timer同步到多个mode 下面
的效果
所以,可以通过addTimer到一个commonMode上面来实现把一个Timer添加到多个mode下面,同时UITrackingRunLoopMode是被打上common标记的,就可以把timer同步到了UITrackingRunLoopMode上,如果列表继续滑动,timer是可以正常响应的

所以,如果我们在设置Timer模式时
[[NSRunLoopcurrentRunLoop] addTimer:self.timerforMode:NSDefaultRunLoopMode];
当我们不与UI进行交互时NSTimer有效
当我们与UI交互那么主线程runloop就会转到UITrackingRunLoopMode模式下,不能处理timer,就失效了.
所以我们可以

  1. [[NSRunLoopcurrentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
  2. 开启新线程,在新线程中定义timer,这样就会被新线程的runloop处理。
怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作

在用户进行滑动的过程中,当前的RunLoop运行在UITrackingRunLoopMode模式下,而我们一般对网络请求是放在子线程中,子线程返回给主线程的数据要抛给主线程用来更新UI,可以把这部分逻辑包装起来,提交到主线程的default模式下,这样的话,当用户滑动时,default模式下的任务不会执行,当用户手停止时,mode就切换到了default模式下,就会处理子线程的数据了,这样就不会打断用户的滑动操作了

RunLoop与多线程
  1. 线程和Runloop是一一对应的

  2. 自己创建的线程默认是没有RunLoop的,需要自己手动创建某个线程的RunLoop

实现常驻线程:
  1. 为当前线程开启一个RunLoop
    可以通过[CFRunLoop getCurrent]或者 [NSRunLoop currentRunLoop]来创建,因为获取RunLoop这个方法本身会查找,如果当前线程没有runloop,会在系统内部为我们创建
  2. 向该RunLoop中添加一个port / Source等维护RunLoop的事件循环
    RunLoop如果没有事件需要处理的话,默认情况下,是不能自己维持事件循环,会直接退出,所以需要添加port / Source来维持事件循环机制
  3. 启动该RunLoop
    调用run方法

#import "MCObject.h"

@implementation MCObject
//定义两个k静态全局变量

// 自定义线程
static NSThread *thread = nil;
// 标记当前线程是否要继续事件循环
static BOOL runAlways = YES;

+ (NSThread *)threadForDispatch{
    if (thread == nil) {
        @synchronized(self) {
            if (thread == nil) {//采用线程安全的方式去创建thread,入口方法为runRequest
                // 线程的创建
                thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
                [thread setName:@"com.imooc.thread"];
                // 启动
                [thread start];
            }
        }
    }
    return thread;
}

+ (void)runRequest
{
    // 创建一个Source
    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    
    // 为thread线程创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    
    //while循环维持RunLoop的事件循环
    // 如果可以运行
    while (runAlways) {
        //确保每次yRunLoop运行一圈的时候能够对内存进行释放
        @autoreleasepool {
            /* 令当前RunLoop运行在DefaultMode下面,注意这个运行的mode和上面添加资源的mode必须是同一个mode
               否则把事件源添加到另一个mode上,而运行的defaultMode下,是无法维持运行的
               函数内部会调用mach_msg,发生由用户态到核心态的切换,当前线程就会休眠,就停在里面,不是死循环
               1.0e10是让循环运行到指定时间退出,这个代表很久远的时间
               true代表资源被处理后是否马上返回
             */
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
        }
    }
    
    // 如果Runloop的mode中没有对应的事件源可以处理,runloop就会自动退出
    // 所以我们在某一时机 将source移除,静态变量runAlways = NO时 可以保证跳出RunLoop,线程退出并s释放掉
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

@end

相关文章

  • RunLoop篇-事件循环的实现机制

    在Runloop启动后,首先会发出一个通知.告知观察者即将启动,之后将要处理Timer / Source0事件,然...

  • iOS-RunLoop

    本文主要内容: 概念 数据结构 事件循环的实现机制 RunLoop与NSTimer RunLoop与线程 源码 一...

  • iOS RunLoop

    概念 数据结构 事件循环机制 Runloop 与 NSTimer 什么是RunLoop 是通过内部维护事件循环来对...

  • 问题:什么是 Runloop?

    RunLoop 即运行循环机制。就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。使用 RunLoop ...

  • iOS中的Runloop

    摘要 本文介绍iOS中的事件循环Runloop; Runloop 是什么 Runloop是事件接收和分发机制的一个...

  • Runtime和Runloop的区别

    Runtime和Runloop的区别 一.RunLoop机制: Runloop是事件接收和分发机制的一个实现。 R...

  • Runloop-事件循环实现机制

    runloop启动后,会首先发送一个通知告诉观察者即将进入runloop;之后runloop会向观察者发送一个即将...

  • [iOS面试]第7章 RunLoop相关面试问题

    本文主讲RunLoop相关面试问题,包括RunLoop概念、数据结构、事件循环机制、RunLoop与NSTimer...

  • RunLoop

    概念 数据结构 事件循环机制 RunLoop 与 NSTimer RunLoop 与多线程 一、概念 RunLoo...

  • RunLoop 相关知识点

    RunLoop是开源的:RunLoop源码下载地址 RunLoop是事件接收和分发机制的一个实现,是消息机制的处理...

网友评论

      本文标题:RunLoop篇-事件循环的实现机制

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