美文网首页
运行循环- Runloop

运行循环- Runloop

作者: _既白_ | 来源:发表于2017-05-11 11:44 被阅读19次

    程序启动的原理和过程

    程序启动原理图

    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就是控制线程生命周期并接收事件进行处理的机制。RunLoopiOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。使系统更加流畅、省电、响应快,用户体验好。

    OSX/iOS 系统中,提供了两个这样的对象:NSRunLoopCFRunLoopRef
    CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的API,所有这些API 都是线程安全的。
    NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些API 不是线程安全的。

    Runloop理解

    进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,RunLoop就会暂时停下流水线,节约资源。
    RunLoop管理流水线,流水线(线程)才不会因为无所事事被工厂(进程)销毁;而不需要流水线时,就会辞退RunLoop这个主管,即退出线程,把所有资源释放。

    RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop的循环机制实现,AndroidLooper就是类似的机制。

    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 类型,可以精确的参数,不用以来runloopmode,性能消耗更小。
    dispatch_source_set_timer(dispatch_source_t source, // 定时器对象
                                  dispatch_time_t start, // 定时器开始执行的时间
                                  uint64_t interval, // 定时器的间隔时间
                                  uint64_t leeway // 定时器的精度
                                  );
    
    • CADisplayLink
      Timertolerance表示最大延期时间,如果因为阻塞错过了这个时间精度,这个时间点的回调也会跳过去,不会延后执行。
      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,并在回调里执行这个 blockRunloop只处理主线程的blockdispatch 到其他线程仍然是由 libDispatch处理的。

    • NStime(略)

    • 网络请求
      关于网络请求的接口:最底层是CFSocket层,然后是CFNetwork将其封装,然后是NSURLConnectionCFNetwork进行面向对象的封装,NSURLSessioniOS7 中新增的接口,也用到NSURLConnectionloader线程。所以还是以NSURLConnection为例。
      当开始网络传输时,NSURLConnection 创建了两个新线程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private。其中 CFSocket线程是处理底层socket连接的。NSURLConnectionLoader这个线程内部会使用 RunLoop 来接收底层 socket的事件,并通过之前添加的 Source0通知到上层的 Delegate

    Runloop的应用

    • 滑动与图片刷新;
      tableviewcell上有需要从网络获取的图片的时候,滚动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自动退出。

    相关文章

      网友评论

          本文标题:运行循环- Runloop

          本文链接:https://www.haomeiwen.com/subject/eyantxtx.html