您的位置:首页 > 移动开发 > IOS开发

iOS程序启动与运转-Runloop

2016-03-10 18:55 399 查看
学习iOS开发一般都是从UI开始的,从只知道从IB拖控件,到知道怎么在方法里写代码,然后会显示什么样的视图,产生什么样的事件,等等。其实程序从启动开始,一直都是按照苹果封装好的代码运行着,暴露的一些属性和方法作为接口,是让我们在给定的方法里写代码实现自定义功能,做出各种各样的应用。这些方法的调用顺序最为关键,熟悉了程序运转和方法调用的顺序,才可以更好地操控程序和代码,尽量避免Xcode不报错又实现不了功能的BUG。从Xcode的线程函数调用栈可以看到一些方法调用顺序。

0从程序启动开始到view显示:

start->(加载framework,动态静态链接库,启动图片,Info.plist,pch等)->main函数->UIApplicationMain函数:





UIApplication:

通过window管理视图;

发送Runloop封装好的control消息给target;

处理URL,应用图标警告,联网状态,状态栏,远程事件等。

AppDelegate:

管理UIApplication生命周期和应用的五种状态(notRunning/inactive/active/background/suspend)。

KeyWindow:

显示view;

管理rootViewcontroller生命周期;

发送UIApplication传来的事件消息给view。

rootViewController:

管理view(view生命周期;view的数据源/代理;view与superView之间事件响应nextResponder的“备胎”);

界面跳转与传值;

状态栏,屏幕旋转。

view:

通过作为CALayer的代理,管理layer的渲染(顺序大概是先更新约束,再layout再display)和动画(默认layer的属性可动画,view默认禁止,在UIView的block分类方法里才打开动画)。layer是RGBA纹理,通过和mask位图(含alpha属性)关联将合成后的layer纹理填充在像素点内,GPU每1/60秒将计算出的纹理display在像素点中。

布局子控件(屏幕旋转或者子视图布局变动时,view会重新布局)。

事件响应:event和guesture。

插播控制器生命周期

runloop:

(要让马儿跑)通过do-while死循环让程序持续运行:接收用户输入,调度处理事件时间。

(要让马儿少吃草)通过mach_msg()让runloop没事时进入trap状态,节省CPU资源。

关于程序启动原理以及各个控件的资料,已经有太多资料介绍,平时我们也经常接触经常用到,但关于Runloop的资料,官方文档总是太过简练,网上资源说法也不太统一,只能从CFRunLoopRef开源代码着手,试着学习总结下。(NSRunloop是对CFRunloopRef的面向对象封装,但是不是线程安全)。

1Runloop

1、与线程和自动释放池相关:

2、CFRunLoopRef构造:数据结构;创建与退出;mode切换和item依赖;Runloop启动

-CFRunLoopModeRef:数据结构(与CFRunLoopRef放一起了);创建;类型;

modeItems:-CFRunLoopSourceRef:数据结构(source0/source1);

-source0:

-source1:

-CFRunLoopTimerRef:数据结构;创建与生效;相关类型(GCD的timer与CADisplayLink)

-CFRunLoopObserverRef:数据结构;创建与添加;监听的状态;

3、Runloop内部逻辑:关键在两个判断点(是否睡觉,是否退出)

-代码实现:

-函数作用栈显示:

4、Runloop本质:machport和mach_msg()。

5、如何处理事件:

-界面刷新:

-手势识别:

-GCD任务:

-timer:(与CADisplayLink)

-网络请求:

6、应用:

-滑动与图片刷新;

-常驻子线程,保持子线程一直处理事件

-滑动与图片刷新;

Runloop

1、与线程和自动释放池相关:

Runloop的寄生于线程:一个线程只能有唯一对应的runloop;但这个根runloop里可以嵌套子runloops;

自动释放池寄生于Runloop:程序启动后,主线程注册了两个Observer监听runloop的进出与睡觉。一个最高优先级OB监测Entry状态;一个最低优先级OB监听BeforeWaiting状态和Exit状态。

线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)

2、CFRunLoopRef构造:

数据结构:





创建与退出:mode切换和item依赖

a主线程的runloop自动创建,子线程的runloop默认不创建(在子线程中调用NSRunLoop*runloop=[NSRunLoopcurrentRunLoop];

获取RunLoop对象的时候,就会创建RunLoop);

brunloop退出的条件:app退出;线程关闭;设置最大时间到期;modeItem为空;

c同一时间一个runloop只能在一个mode,切换mode只能退出runloop,再重进指定mode(隔离modeItems使之互不干扰);

d一个item可以加到不同mode;一个mode被标记到commonModes里(这样runloop不用切换mode)。

启动Runloop:





CFRunLoopModeRef:

数据结构(见上);

创建添加:runloop自动创建对应的mode;mode只能添加不能删除





类型:

1.kCFRunLoopDefaultMode:默认mode,通常主线程在这个Mode下运行。

2.UITrackingRunLoopMode:追踪mode,保证Scrollview滑动顺畅不受其他mode影响。

3.UIInitializationRunLoopMode:启动程序后的过渡mode,启动完成后就不再使用。

4:GSEventReceiveRunLoopMode:Graphic相关事件的mode,通常用不到。

5:kCFRunLoopCommonModes:占位mode,作为标记DefaultMode和CommonMode用。

modeItems:





A--CFRunLoopSourceRef:事件来源

按照官方文档CFRunLoopSourceRef为3类,但数据结构只有两类(???)

Port-BasedSources:与内核端口相关

CustomInputSources:与自定义source相关

CocoaPerformSelectorSources:与PerformSEL方法相关)

数据结构(source0/source1);





source0:event事件,只含有回调,需要标记待处理(signal),然后手动将runloop唤醒(wakeup);

source1:包含一个mach_port和一个回调,被用于通过内核和其他线程发送的消息,能主动唤醒runloop。

B--CFRunLoopTimerRef:系统内“定时闹钟”

NSTimer和performSEL方法实际上是对CFRunloopTimerRef的封装;runloop启动时设置的最大超时时间实际上是GCD的dispatch_source_t类型。

数据结构:





创建与生效;





相关类型(GCD的timer与CADisplayLink)

GCD的timer:

dispatch_source_t类型,可以精确的参数,不用以来runloop和mode,性能消耗更小。





CADisplayLink:

Timer的tolerance表示最大延期时间,如果因为阻塞错过了这个时间精度,这个时间点的回调也会跳过去,不会延后执行。

CADisplayLink是一个和屏幕刷新率一致的定时器,如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和NSTimer相似,只是没有tolerance容忍时间),造成界面卡顿的感觉。

C--CFRunLoopObserverRef:监听runloop状态,接收回调信息(常见于自动释放池创建销毁)

数据结构:





创建与添加;





监听的状态;





3、Runloop内部逻辑:关键在两个判断点(是否睡觉,是否退出)

代码实现:





函数作用栈显示:





一步一步写具体的实现逻辑过于繁琐不便理解,按Runloop状态大致分为:

1-Entry:通知OB(创建pool);

2-执行阶段:按顺序通知OB并执行timer,source0;若有source1执行source1;

3-休眠阶段:利用mach_msg判断进入休眠,通知OB(pool的销毁重建);被消息唤醒通知OB;

4-执行阶段:按消息类型处理事件;

5-判断退出条件:如果符合退出条件(一次性执行,超时,强制停止,modeItem为空)则退出,否则回到第2阶段;

6-Exit:通知OB(销毁pool)。

4、Runloop本质:machport和mach_msg()。

Mach是XNU的内核,进程、线程和虚拟内存等对象通过端口发消息进行通信,Runloop通过mach_msg()函数发送消息,如果没有port消息,内核会将线程置于等待状态mach_msg_trap()。如果有消息,判断消息类型处理事件,并通过modeItem的callback回调(处理事件的具体执行是在DoBlock里还是在回调里目前我还不太明白???)。

Runloop有两个关键判断点,一个是通过msg决定Runloop是否等待,一个是通过判断退出条件来决定Runloop是否循环。

5、如何处理事件:

界面刷新:

当UI改变(Frame变化、UIView/CALayer的继承结构变化等)时,或手动调用了UIView/CALayer的setNeedsLayout/setNeedsDisplay方法后,这个UIView/CALayer就被标记为待处理。

苹果注册了一个用来监听BeforeWaiting和Exit的Observer,在它的回调函数里会遍历所有待处理的UIView/CAlayer以执行实际的绘制和调整,并更新UI界面。

事件响应:

当一个硬件事件(触摸/锁屏/摇晃/加速等)发生后,首先由IOKit.framework生成一个IOHIDEvent事件并由SpringBoard接收,随后由machport转发给需要的App进程。

苹果注册了一个Source1(基于machport的)来接收系统事件,通过回调函数触发Sourece0(所以UIEvent实际上是基于Source0的),调用_UIApplicationHandleEventQueue()进行应用内部的分发。

_UIApplicationHandleEventQueue()会把IOHIDEvent处理并包装成UIEvent进行处理或分发,其中包括识别UIGesture/处理屏幕旋转/发送给UIWindow等。

手势识别:

如果上一步的_UIApplicationHandleEventQueue()识别到是一个guesture手势,会调用Cancel方法将当前的touchesBegin/Move/End系列回调打断。随后系统将对应的UIGestureRecognizer标记为待处理。

苹果注册了一个Observer监测BeforeWaiting(Loop即将进入休眠)事件,其回调函数为_UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的GestureRecognizer,并执行GestureRecognizer的回调。

当有UIGestureRecognizer的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

GCD任务:

当调用dispatch_async(dispatch_get_main_queue(),block)时,libDispatch会向主线程的RunLoop发送消息,RunLoop会被唤醒,并从消息中取得这个block,并在回调里执行这个block。Runloop只处理主线程的block,dispatch到其他线程仍然是由libDispatch处理的。

timer:(见上modeItem部分)

网络请求:

关于网络请求的接口:最底层是CFSocket层,然后是CFNetwork将其封装,然后是NSURLConnection对CFNetwork进行面向对象的封装,NSURLSession是iOS7中新增的接口,也用到NSURLConnection的loader线程。所以还是以NSURLConnection为例。

当开始网络传输时,NSURLConnection创建了两个新线程:com.apple.NSURLConnectionLoader和com.apple.CFSocket.private。其中CFSocket线程是处理底层socket连接的。NSURLConnectionLoader这个线程内部会使用RunLoop来接收底层socket的事件,并通过之前添加的Source0通知到上层的Delegate。



6、应用:

滑动与图片刷新;

当tableview的cell上有需要从网络获取的图片的时候,滚动tableView,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,但是会造成卡顿。可以让设置图片的任务在CFRunLoopDefaultMode下进行,当滚动tableView的时候,RunLoop是在UITrackingRunLoopMode下进行,不去设置图片,而是当停止的时候,再去设置图片。





常驻子线程,保持子线程一直处理事件

为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出。





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: