开始

很久之前就看了一次YY的文章,没看懂。后来又看了sunny的视频和叶孤城的直播的视频,找了很多资料,对RunLoop也越来越清晰,然后又看了两三次YY的文章,虽然还没完全看懂,不得不说写的非常好,帮助很大。还有官方文档,学到了很多东西。还有就是github上的一些demo,文中一些代码别人写过了,我就直接拿过来了。文中一些结论也是取自大神的文章或者视频。非常感谢这些前辈大神们的分享吧!!

什么是RunLoop

RunLoop其实就是一种处理事件、消息的机制被面向对象化,它就是一个对象。其实它的本质就是一个do-while循环

  1. do {
  2. //如果有事件/消息,就处理
  3. //没事件/消息或者都处理完了就沉睡
  4. } while(1)

NSRunloop和CFRunloop

NSRunLoop是对CFRunLoop的封装,使之更面向对象化。我们一般在NSRunLoop层面上操作。CFRunLoop基于C语言,从属于CoreFoundation(开源)

官方文档里对CFRunLoop的解释很好,它建立任务来监测着输入源(事件源,事件/消息)和准备好处理输入源的时候调度管理,输入源包括用户输入的设备,网络连接,周期性或者延迟的事件,还有异步的回调等。

RunLoop的结构组成

一个RunLoop可以监测三个对象,Sources,Timers,Observers,如图

图来自YY的文章,这个图很棒

其实是一个RunLoop对象可以包含一个或者多个Mode,这个Mode可以说是一种模式,一种配置。而Mode又包含着输入源(事件源)Source,观察者Observer,和计时器Timer和其他一些信息。

CFRunLoopMode

Mode决定了RunLoop在什么时候处理什么事情,在某个Mode在某个时候能做什么不能做什么,在某个Mode某个时候只能做什么。
切换Mode需要先推出RunLoop再重新进入。

Mode有几种

  • DefaultMode,CFRunLoop的是kCFRunLoopDefaultModeNSRunLoop的是NSDefaultRunLoopMode,这是默认的mode,即APP平常的状态
  • UITrackingRunLoopMode,追踪scrollView滑动的状态,当有scrollView滑动的时候RunLoop会切换到该mode下,滑动结束再切换到其他mode
  • UIInitializationRunLoopMode,这个是私有的,在APP启动时到显示第一个界面期间会进入这个mode
  • NSRunLoopCommonModes,这个是Mode集合,自定义或者若干个集合,默认是包含了defaultMode和trackingMode
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

这里先说一个例子,例如我们创建的NSTimer对象在运行时碰到有scrollView的空间滑动的时候是默认不会继续计时的

  1. [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tiktok) userInfo:nil repeats:YES];
  2. - (void)tiktok
  3. {
  4. NSLog(@"%d",self.count++);
  5. }

那是因为用scheduleTime...这个方法,timer默认被添加到了RunLoopdefaultMode,而滑动tableView的时候RunLoop切换到了trackingMode所以停止了。

解决方法就是把timer添加到当前RunLoopUITrackingRunLoopMode或者NSRunLoopCommonModes

  1. //只要添加一句
  2. [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
  3. //或者
  4. //[[NSRunLoop currentRunLoop] addTimer:timer NSRunLoopCommonModes];

Mode的结构

CFRunLoopMode是一个struct,最主要是SourceTimerObserver,这些被称为mode item,还有一些其他信息,例如name,就是指上面的defaultMode或者trackingMode的名字,还有一些线程锁标志信息等。

  1. struct __CFRunLoopMode {
  2. //Mode的名字
  3. CFStringRef _name;
  4. //Source就两个
  5. CFMutableSetRef _sources0;
  6. CFMutableSetRef _sources1;
  7. //Observer数组
  8. CFMutableArrayRef _observers;
  9. //Timer数组
  10. CFMutableArrayRef _timers;
  11. //还有其他的一些信息
  12. //.....
  13. };

CFRunLoopSource

输入源(input source)是指用户的操作事件(例如点击屏幕)或者网络端口接收到的信息等。而一个CFRunLoopSource对象就是跑在RunLoop上输入源的抽象概念,Source可以说是用户操作的事件、消息和RunLoop的中间层。

有两种Source

  • Source0,被APP所管理,处理内部事件,如UIEvent、CFSocket
  • Source1,被RunLoop和内核所管理,由Mach port驱动,如CFMachPort、CFMessagePort

这里已经涉及到很底层的东西了,查了下Mach port,很底层,太少资料,大概了解到一点是用来通信的通道,端口,这个还有待研究

CFRunLoopObserver

这个就是观察者,同来用来接收各种回调(callbakcs)。一个Observer一次只能被一个RunLoop注册。当RunLoop的状态切换的时候Observer可以观察到

这个是RunLoop的状态

  1. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
  2. kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
  3. kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
  4. kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
  5. kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
  6. kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
  7. kCFRunLoopExit = (1UL << 7), // 即将退出Loop
  8. };

然后我们来测试一下,创建一个Observer来观察RunLoop的状态

  1. //创建一个Observer,观察RunLoop的所有状态
  2. //这里打印出来的数字是上面struct的数字X的2^X
  3. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  4. NSLog(@"RunLoop状态-%zd", activity);
  5. });
  6. CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  7. //注意CF对象要释放
  8. CFRelease(observer);

然后查看打印

后面的数字即刚才RunLoop的状态,对比struct可以看出,APP一运行就先进入RunLoop,然后2/4切换,即处理SourceTimer然后是32,即将进入睡眠状态,再64,即将从睡眠状态中醒来…然后循环两三次到最后32,即将进入睡眠状态,这就是一个APP打开的时候然后没给任何动作

接下来我滑动一下tableView,来看一下打印

先醒过来,处理SourceTimer,然后有个128(即对应上面的2^7),即将退出RunLoop,这是意料之中,因为我滑动tableView把mode切换到了trackingMode,而切换Mode的时候需要先退出mode再从新进入,等松开手后最终状态又变成32,睡眠状态

这也很符合刚开始的do-while循环,一有事件/消息处理就处理,不然就进入睡眠

RunLoop和线程的关系

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。(直接取自YY的结论)

RunLoop和Autorelease Pool

UIkit通过RunLoopObserver在RunLoop两次Sleep间对Autorelease Pool进行Pop和Push将这次Loop中产生的Aotorelease对象释放

应用

tableView滑动后再加载图片

当一个tableView或者collectionView(这两个都是继承自scrollView)里面放了很多图片的时候并且要加载的时候,有一个优化的措施就是在滑动的时候不加载,在滑动完了之后再开始加载。

  1. UIImage *downloadedImage = ...;
  2. self.imageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

(以上代码取自sunny的视频)

让setImage方法只在NSDefaultRunLoopMode里面运行,在滑动的时候进入trackingMode就不执行

还有更具体的实现可以看下这个github demo

虽然可以这样实现,但是我觉得使用性不强,我看了很多较常用的APP都没有这样实现,且当学习吧

常驻线程

  1. [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
  2. [[NSRunLoop currentRunLoop] run];

或者

  1. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];

监测滑动卡顿

前面说到RunLoop会一直切换状态,我们要监测的是在处理Source的时候的时间,如果时间太长,则说明有可能卡顿了

只需要另外再开启一个线程,实时计算这两个状态区域之间的耗时是否到达某个阀值,便能揪出这些性能杀手. 为了让计算更精确,需要让子线程更及时的获知主线程NSRunLoop状态变化,所以dispatch_semaphore_t是个不错的选择,另外卡顿需要覆盖到多次连续小卡顿和单次长时间卡顿两种情景,所以判定条件也需要做适当优化.将上面两个方法添加计算的逻辑如下:`

  1. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
  2. {
  3. MyClass *object = (__bridge MyClass*)info;
  4. // 记录状态值
  5. object->activity = activity;
  6. // 发送信号
  7. dispatch_semaphore_t semaphore = moniotr->semaphore;
  8. dispatch_semaphore_signal(semaphore);
  9. }
  10. - (void)registerObserver
  11. {
  12. CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
  13. CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES, 0,&runLoopObserverCallBack,&context);
  14. CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
  15. // 创建信号
  16. semaphore = dispatch_semaphore_create(0);
  17. // 在子线程监控时长
  18. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  19. while (YES)
  20. {
  21. // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
  22. long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
  23. if (st != 0)
  24. {
  25. if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)
  26. {
  27. if (++timeoutCount < 5)
  28. continue;
  29. NSLog(@"好像有点儿卡哦");
  30. }
  31. }
  32. timeoutCount = 0;
  33. }
  34. });
  35. }

以上带去取自https://github.com/suifengqjn/PerformanceMonitor

最后

写的有点糟,只是一个小总结,感觉还是有好多不懂,还是要深入学习。

RunLoop学习总结的更多相关文章

  1. [New Learn] RunLoop学习-官方译文

    Run Loops Run loops是线程的一个基本构成部分.一个run loop 是一个事件处理循环,你可以使用它来处理线程收到的事件.设计run loop的目的就是可以使得线程在收到事件的时候处 ...

  2. 我的runloop学习笔记

    前言:公司项目终于忙的差不多了,最近比较闲,想起叶大说过的iOS面试三把刀,GCD.runtime.runloop,runtime之前已经总结过了,GCD在另一篇博客里也做了一些小总结,今天准备把ru ...

  3. 2016 - 1 - 20 runloop学习(2)

    一:CFRunLoopModeRef 1. CFRunLoopModeRef带表RunLoop的运行模式 2. 一个Runloop可以有若干个mode,每个mode又包含若干个sourse,timer ...

  4. 2016 - 1 - 20 runloop学习

    一:Runloop基本知识 1.本质就是运行循环 2.基本作用: 2.1保证程序持续运行 2.2处理APP中的各种事件:触摸,定时器,selector... 2.3节省CPU资源,系统程序性能:它会让 ...

  5. 主线程 RunLoop 学习笔记

    以下为主RunLoop 的输出,能够看到不同的source0,source1,observer ---------------------------------- CFRunLoop{wakeup ...

  6. ios runloop学习

    今天突然才之间才意识到NSTimer这样的运行方式,是在多线程中实现的循环还是在主线程中去实现的呢.当然不可能是在主线程中的while那么简单,那样什么都干不了,简单看了下NSTimer是以同步方式运 ...

  7. iOS开发RunLoop学习:一:RunLoop简单介绍

    一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementatio ...

  8. iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题

    一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...

  9. iOS开发RunLoop学习:三:Runloop相关类(source和Observer)

    一:RunLoop相关类: 其中:source0指的是非基于端口por,说白了也就是处理触摸事件,selector事件,source1指的是基于端口的port:是处理系统的一些事件 注意:创建一个Ru ...

随机推荐

  1. (iOS)viewController背景透明化

    #ifdef __IPHONE_8_0 ) { [UIApplication sharedApplication].keyWindow.rootViewController.providesPrese ...

  2. cout输出流的执行顺序

    一道题目: #include <iostream> using namespace std; ; template<typename T> int foo() { int va ...

  3. mySQL中replace的用法

    MySQL replace函数我们经常用到,下面就为您详细介绍MySQL replace函数的用法,希望对您学习MySQL replace函数方面能有所启迪   mysql replace实例说明: ...

  4. windows 2008 远程端口3389修改小记

    修改远程端口使服务器更加安全,win2008上大致与win2003的配置差不多,有些细微的差别,在此小记一下. 简要步骤: 1.打开远程连接功能(默认都是已经打开的) :开始>计算机>属性 ...

  5. C# Coding & Naming Conventions

    Reference document https://msdn.microsoft.com/en-us/library/ff926074.aspx https://msdn.microsoft.com ...

  6. 由命名空间函数而引发思考--js中的对象赋值问题

    最近没有编码任务,作为一个才毕业的小辣鸡,给的任务就是看一下公司的新系统,熟悉怎么用哪些地方是干什么的. 下午喝了两杯水,感觉有点浪.然后就开始看了下代码.发现有一个函数是这样子的. var TX = ...

  7. LeetCode 链表的插入排序

    Sort a linked list using insertion sort 创建一个新的链表,将旧链表的节点插入到正确的位置 package cn.edu.algorithm.huawei; pu ...

  8. UVa---------10935(Throwing cards away I)

    题目: Problem B: Throwing cards away I Given is an ordered deck of n cards numbered 1 to n with card 1 ...

  9. VS2010 .net4.0 登录QQ 获取QQ空间日志 右键选中直接打开日志 免积分 源码下载

    代码有一部分是原来写的  最近翻代码 看到了  就改了一下 CSDN上传源码 上传了几次都没 成功 郁闷   不知道怎么回事 上传不了 想要的留 邮箱 或加群77877965 下载地址在下面 演示地址 ...

  10. werkzeug中服务器处理请求的实现

    当成功建立好服务器后,接下来就是等待请求并处理请求通过路由分配给相应的视图函数了,以下是函数调用过程 -> self._handle_request_noblock() /usr/lib/pyt ...