程序启动的原理和过程
程序启动原理图start
-> (加载framework,动态静态链接库,启动图片,Info.plist等)
-> main函数
-> UIApplicationMain函数
:
- 初始化`UIApplication`单例对象
- 初始化`AppDelegate`对象,并设为`UIApplication`对象的代理
- 检查`Info.plist`设置的`xib`文件是否有效,如果有则解冻`Nib`文件并设置`outlets`,创建显示`key window`、`rootViewController`、与`rootViewController`关联的`根view`(没有关联则看`rootViewController`同名的`xib`),否则`launch`之后由程序员手动加载。
- 建立一个主事件循环,其中包含UIApplication的Runloop来开始处理事件。
Runloop的概念
当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop
就是控制线程生命周期并接收事件进行处理的机制。RunLoop
是iOS
事件响应与任务处理最核心的机制,它贯穿iOS
整个系统。使系统更加流畅、省电、响应快,用户体验好。
OSX/iOS
系统中,提供了两个这样的对象:NSRunLoop
和 CFRunLoopRef
。
CFRunLoopRef
是在 CoreFoundation
框架内的,它提供了纯 C
函数的API
,所有这些API
都是线程安全的。
NSRunLoop
是基于 CFRunLoopRef
的封装,提供了面向对象的 API
,但是这些API
不是线程安全的。
Runloop理解
进程是一家工厂,线程是一个流水线,Run Loop
就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop
就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,RunLoop
就会暂时停下流水线,节约资源。
RunLoop
管理流水线,流水线(线程)才不会因为无所事事被工厂(进程)销毁;而不需要流水线时,就会辞退RunLoop
这个主管,即退出线程,把所有资源释放。
RunLoop
并不是iOS
平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop
的循环机制实现,Android
的Looper
就是类似的机制。
Runloop特性
- 主线程的
RunLoop
在应用启动的时候就会自动创建 - 其他线程则需要在该线程下自己启动
- 不能自己创建
RunLoop
-
RunLoop
并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop
-
RunLoop
负责管理autorelease pools
-
RunLoop
负责处理消息事件,即输入源事件和计时器事件
Runloop的目的
- 使程序一直运行接受用户输入
- 决定程序在何时应该处理哪些
Event
- 调用解耦(对于编程经验为0的完全没搞懂这个意思,解释为
Message Queue
) - 节省
CPU
时间
Runloop与线程和自动释放池相关:
Runloop
的寄生于线程:一个线程只能有唯一对应的runloop
;但这个根runloop
里可以嵌套子runloops
;- 自动释放池寄生于
Runloop
:程序启动后,主线程注册了两个Observer
监听runloop
的pop、push与sleep
。一个最高优先级OB
监测Entry
状态;一个最低优先级OB
监听BeforeWaiting
状态和Exit
状态。 -
线程(创建)
-->runloop将进入
-->最高优先级OB创建释放池
-->runloop将睡
-->最低优先级OB销毁旧池创建新池
-->runloop将退出
-->最低优先级OB销毁新池
-->线程(销毁)
Runtime相关的NSTimer
//NSTimer:
// 创建一个定时器(需要手动加到runloop的mode中)
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// 默认已经添加到主线程的runLoop的DefaultMode中
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// performSEL方法
// 内部会创建一个Timer到当前线程的runloop中(如果当前线程没runloop则方法无效;performSelector:onThread: 方法放到指定线程runloop中)
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
相关类型(GCD的timer与CADisplayLink)
-
GCD的timer:
dispatch_source_t
类型,可以精确的参数,不用以来runloop
和mode
,性能消耗更小。
dispatch_source_set_timer(dispatch_source_t source, // 定时器对象
dispatch_time_t start, // 定时器开始执行的时间
uint64_t interval, // 定时器的间隔时间
uint64_t leeway // 定时器的精度
);
-
CADisplayLink :
Timer
的tolerance
表示最大延期时间,如果因为阻塞错过了这个时间精度,这个时间点的回调也会跳过去,不会延后执行。
CADisplayLink
是一个和屏幕刷新率一致的定时器,如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和NSTimer
相似,只是没有tolerance容忍时间),造成界面卡顿的感觉。
RunLoop处理事件
-
界面刷新:
UI改变
——>标记UI控件处于待处理状态
——>注册监听
——>遍历待处理对象进行处理
——>更新UI
。
当UI改变( Frame变化、 UIView/CALayer 的继承结构变化等)时,或手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理。苹果注册了一个用来监听BeforeWaiting和Exit的Observer,在它的回调函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。 -
事件响应:
程序启动 ——> 用户点击屏幕 ——> 创建事件——>创建自动释放池 ——> Application响应事件 ——> 事件处理完毕销毁自动释放池。
需要特别注意的是:如果没有使用alloc new copy retain 方法而创建了对象,则内部全是使用了autorelease方法。所以使用自动释放池能对这些对象进行及时释放 -
手势识别:
如果上一步的_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
处理的。 -
NStime(略)
-
网络请求
关于网络请求的接口:最底层是CFSocket
层,然后是CFNetwork
将其封装,然后是NSURLConnection
对CFNetwork
进行面向对象的封装,NSURLSession
是iOS7
中新增的接口,也用到NSURLConnection
的loader
线程。所以还是以NSURLConnection
为例。
当开始网络传输时,NSURLConnection
创建了两个新线程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private
。其中CFSocket
线程是处理底层socket
连接的。NSURLConnectionLoader
这个线程内部会使用RunLoop
来接收底层socket
的事件,并通过之前添加的Source0
通知到上层的Delegate
。
Runloop的应用
- 滑动与图片刷新;
当tableview
的cell
上有需要从网络获取的图片的时候,滚动tableView
,异步线程会去加载图片,加载完成后主线程就会设置cell
的图片,但是会造成卡顿。可以让设置图片的任务在CFRunLoopDefaultMode
下进行,当滚动tableView
的时候,RunLoop
是在UITrackingRunLoopMode
下进行,不去设置图片,而是当停止的时候,再去设置图片。
-(void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下执行(刷新图片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
}
- 常驻子线程,保持子线程一直处理事件。
为了保证线程长期运转,可以在子线程中加入RunLoop,并且给Runloop设置item,防止Runloop自动退出。
网友评论