一、RunLoop介绍
1.1概念:(关键字:运循 内维事循对事/消 管 对象)
运行循环,程序运行过程中循环做一些事情。通过内部维护的事件循环对事件/消息进行管理的一个对象。
1.1.2RunLoop休眠实现原理(关键字: 等消息 无线休 有唤醒 用户态->内核态 )
等待消息,没有消息就让线程休眠,用户态到内核态切换;
有消息就唤醒线程,处理事件,内核态到用户态切换。
1.1.3main函数作为启动入口,顺着执行体代码以此执行,最后main函数退出,程序退出。main为什么能保持不退出?正常执行体退出main就退出,而我们的main一直是活跃状态 main函数当中UIApplicationMain( )函数,内部启动RunLoop运行循环,接收消息,如滑动列表,网络请求的返回,然后处理。处理完成后主线程处于休眠状态,继续等待,不是死循环,不是简单for或while,而是有用户态到内核态的状态的切换,避免资源占用。若此时点击屏幕产生machPort,最终转成source1,可以把主线程唤醒,运行,然后处理事件。当程序杀死时,就会发送通知即将退出RunLoop,然后RunLoop退出,线程销毁掉。
1.1.4点击应用图标
1.2应用:(关键字:定 GAMQ 事手界 网 A)
定时器NSTimer、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
网络请求
AutoreleasePool
实际项目中应用:(关键字:线活 解NSTimer 监卡 性优)
控制线程生命周期(保证线程活跃)
解决滑动时NSTimer生效问题
监控应用卡顿
性能优化
1.3作用:(关键字:持运 处事 节CPU提性能)
保持程序持续运行
处理各种事件如触摸、定时器等
节省CPU、提高性能,适时做事或休息
1.4两套访问和使用RunLoop的API
Fundation: NSRunLoop是对CFRunLoopRef的OC封装,提供了面向对象的API
CoreFoundation: CFRunLoopRef开源https://opensource.apple.com/tarballs/CF/
1.5获取方法 获当线current 获主线main
[NSRunLoop currentRunLoop]; CFRunLoopGetCurrent( );
[NSRunLoop mainRunLoop]; CFRunLoopGetMain( );
二、数据结构(关键字:CFRunLoopRef, Mode, Source, Timer, Observer)
CFRunLoopRef
typedef struct __CFRunLoop *CFRunLoopRef;
struct __CFRunLoop{
pthread_t pthread;//一一对应(RunLoop与多线程的关系)
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;//CFRunLoopMode集合
CFMutableSetRef _commonModes;//NSString类型的集合
CFMutableSetRef _commonModeItems;//包含多个Source/Timer/Observer
}
CFRunLoopModeRef(关键字:运模 一对多Mode,一Mode对多Source0/Source1/Timer/Observer Loop启只选一currentMode 切换退重选 无Source0/Source1/Timer/Observer,Loop即退)
typedef__CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode{
CFString _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
}
CFRunLoopSource
source0需要手动唤醒线程 source1具备唤醒线程的能力
CFRunLoopTimer基于事件的定时器
与NSTimer是toll_free bridged可免费桥转换
CFRunLoopObserver观测时间点 进入 即将处理Timer Source 即将休眠 刚从休眠中唤醒 退出
KCFRunLoopEntry/BeforeTimes/BeforeSources/BeforeWaiting/AfterWaiting/Exit
RunLoop的运行模式
各数据结构间关系:一个RunLoop有若干Mode,一个Mode有若干Source0/Source1/Timer/Observer
RunLoop启动只选择一个Mode作为currentMode
若要切换Mode只能退出当前Mode,再重选Mode进入
若Mode没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出 当RunLoop运行在某个Mode上,如Mode1,此时另一个Mode如2中某个observer或timer回调了,此时无法接收到Mode2中回调过来的Observer以及timer事件,这就是RunLoop有多个Mode的原因,起到了屏蔽的效果,当运行在Mode1中只能接收和处理Mode1中的Sources、Observers、Timers
NSRunLoopCommonModes不是实际存在的Mode 是同步Source/Timer/Observer到多个Mode中的一种技术方案
常见2种Mode(Default默主运或 UITracking踪滑不受其他影响)
KCFRunLoopDefaultMode(NSDefaultRunLoopMode)默认Mode,主线程在该Mode下运行
UITrackingRunLoopMode跟踪触摸滑动,保证界面滑动是不受其他Mode影响
CFRunLoopObserverRef(关键字:Entry BeforeTimers BeforeSources BeforeWaiting AferWaiting Exit AllActivities)
typedef CF_OPTIONS(CFOptionFlags,CFRunLoopActivity){
kCFRunLoopEntry = (1UL <<0),
1,
2,
5,
6,
7,
0X0FFFFFFFU
}
三、RunLoop运行逻辑/事件循环机制
(关键字:1/2/3/7/8/11通知Observer 1.进Loop 2.将处理Timer 3.Sources 4.处理Blocks 5.Source0可能再Blocks 6.若存Source1结束休眠 7.无则休 8.结束休眠处理Timer、GAMQ Source1 9.处理Blocks 10.据之前执行结果决定如何操作回2或退出Loop 11.退出RunLoop)
无论调用NSRunLoop还是CFRunLoopRef的run最终都会调用void RunLoopRun( )
1.通知Observer: 进入Loop 即将处理Timer 即将处理Sources
2.处理blocks Source0可能还会处理blocks 若有Sounces1结束休眠
3.通知Observer:若无Sounces1则休眠 有Source1/Timer事件回调/外部手动唤醒等被唤醒结束休眠,处理Timer GCD Async Main Queue
4.处理Blocks 根据之前执行结果决定如何操作:回2或退出RunLoop
5.通知Observer:退出RunLoop
四、RunLoop与NSTimer
滑动tableView时我们的定时器不生效了?怎么解决呢?默认运行在KCFRunLoopDefaultMode,当滑动时发生Mode切换,切换到UITrackingRunLoopMode 可通过CFRunLoopAddTimer函数把timer添加到commonMode(不是实际的mode只是把一些mode打上common标记,然后可以把事件源比如timer同步到多个mode)中
CFRunLoopAddTimer(runLoop, timer,commonMode)函数具体实现:若当前模式名称是commonModes会提取runloop的commonModes,是一堆字符串元素,然后判断其对应的commonModeItems是否为空,若为空,会重新创建集合。然后timer添加到commonModeItems集合,然后把runloop和timer封装到context,之后对集合中每一个元素都调用_CFRunLoopAddItemToCommonModes函数,该函数参数集合中对象元素和context。
CFRunLoopAddItemToCommonModes
Timer如果想同时加入两个Mode应该怎么处理?
五、RunLoop与多线程(关键字:一对一 主自建 子第一次获取建 全局字典 结时销)
5.1每个线程都有与之一一对应的RunLoop
主线程的RunLoop自动创建,子线程默认不开启
子线程在第一次被获取时,创建RunLoop
RunLoop存在于全局字典中,线程作为key,RunLoop作为value
线程结束时RunLoop销毁
5.2怎么实现常驻线程?
为当前线程开启一个RunLoop,
向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环,
启动该RunLoop 运行模式和资源添加的模式必须是同一个,否则可能由于外部使用while循环导致死循环
5.3怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作?
用户滑动过程中,当前RunLoop运行在UITrackingRunLoopMode,而一般对网络请求放在子线程进行,子返回给主的数据主线程用来更新UI,
此时可通过把子线程抛回给主线程进行UI更新的逻辑包装起来,提交到主线程的default模式下,当前用户滑动过程中,处于TrackingRunLoopMode下,分派到default模式下的任务不会执行。
当停止滑动时,当前线程的mode切换到default模式下,会处理子线程上抛给子线程的任务。