iOS 应用,丝般顺滑的理想情况就是 60FPS (对于 iPad Pro 是 240FPS),即在 16ms 之内完成一次渲染。如果找到在每次渲染花费了多久,究竟做了什么事情,那么就可以进行针对性的优化。

RunLoop 的概念

在程序中,我们需要一种机制,可以让当前线程能够随时处理事件但不退出。这种模型通常被称为 Event Loop,在 iOS 中使用 RunLoop 来实现。
RunLoop 管理了所在线程需要处理的事件和消息。当有事情需要处理时,就唤醒当前线程,处理事件。当事情全部处理完毕时,线程处于休眠状态,以避免资源占用。这样子线程一直处于“接受消息->等待->处理”循环中。

根据苹果文档,RunLoop 的内部逻辑如上。RunLoop 有很多种状态,比如 beforeWaitingafterWaiting,当状态发生改变时,就会通知 observer。而触摸事件、定时器、网络请求返回等都是作为 Source0、Source1、Timer 被加到需要处理的队列中,都可以唤醒当前线程的 RunLoop。

RunLoop 与界面更新

当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

即界面更新一定发生在主线程的 RunLoop BeforeWaiting 状态发生时。那么我们可以得出一个结论,如果主线程的 RunLoop 当前事件循环占用的时间过多,超过了 32 ms,那么一定会发生一次掉帧。

Point of Interest

iOS 10 之后,引入了一个新的 API,kdebug_signpost_startkdebug_signpost_end。可以结合 Instrument 的 Point of Interest 功能,用来分析起始到结束的耗时。

//事件段的起始和结束
// Timing an activity (code 7 - "Start Up")
kdebug_signpost_start(7, 0, 0, 0, 0);
[self loadAssets];
kdebug_signpost_end(7, 0, 0, 0, 0);

具体使用方法如上,其中第一个参数用来标识事件的 ID,可以同时分析多个事件。如果某个事件发生了多次,那么就可以得到一个列表,以及这个事件耗时的统计信息。

选择列表中的某个点以后,右键选择,可以过滤掉其他时间的信息。

这样子,结合 Time Profile 功能,就可以看到某个事件耗时,以及究竟哪些代码耗时。

结合 RunLoop 与 Point of Instrument 功能

为主线程的 RunLoop 增加一个 observer,并监听特定的状态变化,就找出每一个 RunLoop 循环究竟花费了多长事件。

使用 Instrument,可以得到下面的图。

这样子可以看到每一个 RunLoop 耗时多少,耗时在哪里。找出 Top 问题,针对性优化。

System Trace

time profile 只是查看 CPU 的执行情况,如果一个线程长时间得不到调度,在 time profile 里得不到相应的信息。这时需要用到 System Trace 这个工具。
使用 system trace 时,会记录最近 5s 的 kernel trace,然后分析 Scheduling activity、System calls、Virtual memory operations 等信息。如果可以卡顿可以复现,那么就可以找出来锁等待、死锁、系统调用造成的卡顿问题。
如下图就是由于线程调度造成的卡顿问题。可以看到主线程被 block 了 1 秒多,原因是调用了 AudioSession 相关的函数。

利用 RunLoop 进行优化

找到每一个 RunLoop 中耗时之后,就可以针对性优化,比如主线程读写、懒加载、异步布局之类。也可以把比较复杂的任务分解到不同的 RunLoop 中,这样子 RunLoop 循环的时间不会太长,可以更快响应事件。
具体做法可以参考 texture 这个组件。下面是 copy 过来的代码。

  1. 为主线程的 RunLoop 增加一个 Source

    _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
    CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes);
  2. 如果需要,把事件加入一个队列中,等待下一个 RunLoop 处理

      [renderQueue enqueue:node];
  3. Runloop 进入 kCFRunLoopBeforeWaiting 状态时,判断队列中是否有待处理的事情。如果有,唤醒 Source,使得 RunLoop 马上进入下一个时间循环

    if (!isQueueDrained) {
    CFRunLoopSourceSignal(_runLoopSource);
    CFRunLoopWakeUp(_runLoop);
    }

参考

结合 RunLoop 和 Instrument 定位卡顿的更多相关文章

  1. UE手游如何应对CPU帧率瓶颈和卡顿?

    如何高效准确详细的对性能进行剖析?腾讯游戏学院专家Leonn将归纳总结在UE下对每一性能指标的剖析方法,本文重点讲解如何应对CPU帧率瓶颈和卡顿? CPU上帧率低和卡顿是性能优化中最易出现的一部分,尤 ...

  2. APP测试工具之TraceView卡顿检测

    Traceview卡顿检测 Traceview是Android平台特有的数据采集和分析工具,集成在DDMS工具中,可以采集程序中的方法执行耗时.调用关系.调用次数以及资源占用等情况. 一.使用方法 1 ...

  3. 想让安卓app不再卡顿?看这篇文章就够了

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由likunhuang发表于云+社区专栏 实现背景 应用的使用流畅度,是衡量用户体验的重要标准之一.Android 由于机型配置和系统的 ...

  4. iOS 性能优化总结

    卡顿产生的原因 在 VSync信号到来后,系统图形服务会通过 CADisplayLink等机制通知 App,App主线程开始在 CPU中计算显示内容,比如视图的创建.布局计算.图片解码.文本绘制等.随 ...

  5. 80.Android之内存管理

    转载:http://www.jianshu.com/p/9fb0a795da93 1. Android中的内存 1.1 Android中的垃圾回收机制 Android 平台最吸引开发者的一个特性:有垃 ...

  6. 【行业交流】2016 TiD质量竞争力大会——移动互联网测试到质量的转变之路

    ◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5691233.html TiD质量大会在北京召开,有幸去参加 ...

  7. android app性能优化大汇总(UI渲染性能优化)

    UI性能测试 性能优化都需要有一个目标,UI的性能优化也是一样.你可能会觉得“我的app加载很快”很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交互心理学的角度来考虑 ...

  8. Android UI性能优化详解

    设计师,开发人员,需求研究和测试都会影响到一个app最后的UI展示,所有人都很乐于去建议app应该怎么去展示UI.UI也是app和用户打交道的部分,直接对用户形成品牌意识,需要仔细的设计.无论你的ap ...

  9. Python爬取CSDN博客文章

    0 url :http://blog.csdn.net/youyou1543724847/article/details/52818339Redis一点基础的东西目录 1.基础底层数据结构 2.win ...

随机推荐

  1. 如果程序集是从 Web 上下载的,即使它存储于本地计算机,Windows 也会将其标记为 Web 文件,http://go.microsoft.com/fwlink/?LinkId=179545

    使用Silverlight,经常弄出很多莫名的XXX文件来于Web,神马信任程序集,就Build个程序都那么麻烦,应该在所有发布时注明一些最基本的配置说明,最BT莫过于连下载程序集的地方都找不到. 若 ...

  2. 20155206 2016-2017-2 《Java程序设计》第9周学习总结

    20155206 2016-2017-2 <Java程序设计>第9周学习总结 教材学习内容总结 第十六章 JDBC简介 撰写应用程序是利用通信协议对数据库进行指令交换,以进行数据的增删查找 ...

  3. [VC++入门]指针一

    俗话说没有搞清楚指针就没有学会C/C++,所以指针是一个相当重要的东东,相当年在用 C#调用C++写的动态链接库时,以为C++中的指针就是C#中的引用类型(ref),但是看了一下却不是这样.指针当然和 ...

  4. html自适应布局,@media screen,媒体查询

    html自适应布局,@media screen,媒体查询 自适应代码示例: <!doctype html> <html> <head> <meta chars ...

  5. css3美化滚动条样式

    1.改变浏览器默认的滚动条样式 ::-webkit-scrollbar-track-piece { //滚动条凹槽的颜色,还可以设置边框属性 background-color:#f8f8f8; } : ...

  6. SSH整合 第四篇 Spring的IoC和AOP

    这篇主要是在整合Hibernate后,测试IoC和AOP的应用. 1.工程目录(SRC) 2.IoC 1).一个Service测试类 /* * 加入spring容器 */ private Applic ...

  7. (线段树)Just a Hook -- hdu -- 1689

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=1698 思路: 我的想法很简单,像上一题一样从后面向前面来算,前面已经覆盖的,后面自然不能再来计算了,具体 ...

  8. PAT甲 1032. Sharing (25) 2016-09-09 23:13 27人阅读 评论(0) 收藏

    1032. Sharing (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue To store Engl ...

  9. oracle中row_number() over()分析函数用法

    row_number()over(partition by col1 order by col2)表示根据col1分组,在分组内部根据col2排序,而此函数计算的值就表示每组内部排序后的顺序编号(组内 ...

  10. OpenCV中GPU函数

    The OpenCV GPU module is a set of classes and functions to utilize GPU computational capabilities. I ...