一:什么是runloop
事件循环,绝对不止是死循环这么简单的一个回答。实质上就是runloop内部状态的转换。
1.用户态:应用程序都是在用户态,平时开发用到的api等都是用户态的操作
2.内核态:系统调用,牵涉到操作系统,底层内核相关的指令。
实际上是计算机内部进行的资源调度操作。
1.等待:其实就是用户态-内核态的转换。事件循环不是while死循环,而是状态转换,切记。
二:runloop的数据结构
NSRunloop
CFRunloop (开源)
http://opensource.apple.com/source/CF/CF-1151.16/
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*>内部的每一个字符串的名称对应了每一中Mode。
1)
kCFRunLoopDefaultMode
:
App的默认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*>内部的每一个字符串的名称对应了每一中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下的更新操作就能执行了 、。
如果你喜欢这篇文章,或者有任何疑问,可以扫描第一个二维码,加楼主好友哦
也可以扫第二个二维码,关注楼主个人微信公众号。这里有很多生活,职业,技术相关的文章哦。欢迎您的到来。
微信号: 公众号