iOS runloop的深入浅出,runloop的理解看这里就够了

一:什么是runloop




事件循环,绝对不止是死循环这么简单的一个回答。实质上就是runloop内部状态的转换。
1.用户态:应用程序都是在用户态,平时开发用到的api等都是用户态的操作
2.内核态:系统调用,牵涉到操作系统,底层内核相关的指令。

实际上是计算机内部进行的资源调度操作。







1.等待:其实就是用户态-内核态的转换。事件循环不是while死循环,而是状态转换,切记。

二:runloop的数据结构
NSRunloop


CFRunloop (开源)
1.CFRunloop
1.CFRunloopMode
1.Source/Timer/Observer


1.pthead      一一对应,runloop内部一个线程
2.currentMode   当前mode  CFRunloopMode数据结构



  2.1 name  字符串  比如NSDefaultRunloopMode  别名定义,字符串名称 去找到对应的mode
  2.2 source0,source1 都是无序集合
        2.2.1  source0  需要手动唤醒线程  非基于Port的,用于用户主动触发的事件 
    常见的source事件:比如用户点击按钮,拖拽,手势等事件。
           source1  具备唤醒线程能力 通过内核和其它线程相互发送消息
     
  2.3 timers都是数组 
  2.4  Observers   CFRunloopObserver观测时间点
     kCFRunloopEntry                 准备启动runloop
     KCFRunloopBeforeTimers   通知观察者将要处理timer了
     kCFRunloopBeforeSources 通知观察者将要处理source了
     kCFRunloopBeforeWaiting   将要休眠了 之后就是用户态切换到内核态
     kCFRunloopAfterWaiting     用户态切换到内核态不久
     kCFRunloopExit                   runloop退出
     kCFRunloopAllActivities       观察所有事件

3.modes    NSMutableSet<CFRunloopMode *>的集合
4.commonModes  NSMutableSet<NSString *>内部的每一个字符串的名称对应了每一中Mode。

 1)kCFRunLoopDefaultModeApp的默认Mode,通常主线程是在这个Mode下运行
    2)UITrackingRunLoopMode界面跟踪 Mode,用于 ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode 影响
    3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
    4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
    5)kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode


5.commonModeItems
 里面有多个Observer,Timer,Source


三:各个数据结构的关系



1.一个runloop对应了多种mode   ,每个mode下又有多种source,timer。Observer
2.每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
 3.如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
 




runloop在同一时间,只能处理,一种mode下的事件

1.例子解释。。滑动tableView的时候,timer事件的轮播器不走了。
 分析: 到底是什么原因。
  解决办法:大家都知道,timer的mode从defaultmode,换到CommonMode就可以了。

 问题:为什么为什么就可以了。

2.先介绍下CommonMode
 



CommonMode并不是一种实际的模式,和defaultmode完全不是一回事。简单的理解就是,如果设置了CommonMode模式,那么runloop在切换mode的同时,也把timer的事件也带走了,所以无论切到哪种mode下,timer的事件都是可以处理的。

回答tableView滚动timer不走问题:
1.默认滚动事件和timer事件都是在kCFRunloopDefaultMode下的
2.此时tableView一滚动,mode切换到UITrackingRunloopMode中,上面说,runloop同一时间只能处理一种mode的事件,那么在DefaultMode的timer就无法响应了。
3.CommonMode又是一个同步多种mode的技术方案,此模式下,当runloop到UITrackingRunloopMode的时候,他把timer的事件也转移到UITrackingRunloopMode下,那么timer就能走了。


中间穿插: GCD的定时器不受Runloop的mode的影响
1.队dispatch_queue_t dispatchQueue = 
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2.创建
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,dispatchQueue);
3.dispatch_source_set_timer
4.dispatch_source_set_event_handler


最后:你是怎么知道CommonMode不是一种实际的模式
 源码分析:VoidCFRunloopAddTimer(runloop,timer,commoMode)
 这个方法流程:
1.大致意思就是,如果是commonmodes将runloop和commonmode封装成一个context。
 2.commonModes  NSMutableSet<NSString *>内部的每一个字符串的名称对应了每一中Mode。在介绍下,这是一个集合。也就是一个set,里面包含了所有mode下的字符串。
3.将set,和contex作为参数,调用__CFRunloopAddItemToCommonModes方法。
4.这里面我们能取到runloop具体在什么mode下,然后再重新调用VoidCFRunloopAddTimer(runloop,timer,commoMode)。
注意:这里不会进行循环调用,因为此时标记的CommonMode已经变成了具体的UITrackingRunloopMode,我们将timer加到UITrackingRunloopMode,timer就能响应了。


四:事件循环的实现机制是

当你被问到,runloop具体操作流程的时候,你该怎么回答。
当我们手动点击屏幕,UI操作,实际runloop是这样来的






那么内核态和用户态的转换,实质是调用了mach_msg()函数。


五:runloop和多线程之间的关系。怎么实现一个常驻线程。

三步走,看图。






1.单例方法中开启线程




2.线程执行的方法,runRequest方法,那么我们在runRequest中添加port和source就实现了常驻线程。

遗漏代码:
// 标记是否要继续时间循环
static BOOL runAlways = YES;



我的疑惑:那么我们用GCD开启子线程的时候,runLoop是怎么维护的呢。

系统自动帮我们解决了。



五:如何实现一个常驻线程,那么由怎么销毁一个常驻线程。
   说明:为什么要实现常驻线程,因为可能有些操作,需要多次在子线程中执行,但是我们不想每次都开启执行一次,结束了。。一句话,多处使用场景,让子线程runloop一直存在,节省内存开销。

1.先懒加载一个线程

2.在run方法中实现runloop一直存在



第一种方式


移除方法
在上面addSource中加入这个

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
        case kCFRunLoopBeforeWaiting:
            NSLog(@"即将进入睡眠");
            // 当runloop进入空闲时,即方法执行完毕后,判断runloop的开关,如果关闭就执行关闭操作
        {
            if (stop) {
                NSLog(@"关闭runloop");
                // 移除runloop的source
                CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
                CFRelease(source);
                // 没有source的runloop是可以通过stop方法关闭的
                CFRunLoopStop(CFRunLoopGetCurrent());
            }
        }
    }
}

第二种方式

这种:NSPort对象在添加在runloop的port中后,引用计数会+3,然后remove后引用计数-2,莫名其妙多出来一个,导致内存也有泄漏的问题
所以不推荐。

第三种方式

先说下这种的移除方式:
1.self->_thread->runloop->timer(addtimer操作引用)->self(time的test事件)  循环引用,大环循环引用,内存泄漏。 首先要想到如何规避。
2.所以此方法不建议使用,
3.当然你知道怎么解决,就用 [timer invalidate]; 

用法:用当前线程处理事情的时候

if (stop) {
    NSLog(@"移除runloop的source");
    [timer invalidate];
} else if (doMethod) {
    [self testMethod];
}

第四种方式

1.这种取巧模式,不知道怎么搞。
  再次使用的时候,
 if(stop) {
  操作一番:
}

六、CF的内存管理(CoreFoundation)
  1. 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
    比如CFRunLoopObserverCreate
    release函数:CFRelease(对象);





总结: 1.runloop怎样做到有事做事,没事休息。
             回答:不要说运行循环,说是用户态到内核态,内核态到用户态的转换。

2.怎样保证子线程数据回来更新UI的时候,不打断用户的滑动操作。
  第一点:tableView在滑动,处于UITrackingRunloopMode模式下。
  第二点:子线程请求的数据,那么在和主线程处理的时候,我们将更新的逻辑加载defaultMode下。那么defaultMode下的操作是不会执行的。
 第三点:滑动结束了,runloop由UITrackingRunloopMode又回到defaultMode下了,那么defaultMode下的更新操作就能执行了 、。


如果你喜欢这篇文章,或者有任何疑问,可以扫描第一个二维码,加楼主好友哦

也可以扫第二个二维码,关注楼主个人微信公众号。这里有很多生活,职业,技术相关的文章哦。欢迎您的到来。

微信号:                                             公众号


发布了72 篇原创文章 · 获赞 26 · 访问量 19万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览