1.死锁: 主线程拿到锁A, 需要获取锁B, 而同时子线程拿了锁B, 需要锁A, 这时主线程等待锁B的释放, 子线程等待锁A的释放, 相互等待.

2.抢锁: 主线程需要访问DB, 而这时某个子线程往DB插入数据. 通常抢锁的体验就是卡顿一阵子就恢复了.

3.主线程大量IO(文件操作): 主线程为了方便直接写入大量数据, 导致页面卡顿.

4.主线程大量计算: 程序中的算法不合理, 大量循环等操作, 导致主线程某个函数占用大量CPU.

5.大量的UI绘制: 复杂的UI, 图文混排等, 带来大量的UI绘制.

卡断如何定位

1.死锁一般会伴随Crash, 我们可以通过Crash日志进行分析.

2.抢锁的问题不太好办, 我们能将锁等待的时间打印出来, 但我们还需要知道是谁占用了锁, 可以检测Runloop的执行,观察耗时.

3.大量的IO可以在函数开始结束打点, 将函数占用时间打到日志中.

4.线程大量计算同理也可以将耗时记录到日志中.

5.大量UI绘制一般是难免的, APP中总会有复杂页面的绘制, 我们可以用AsnycDisplayKit等框架进行预排版,异步绘制,图片解码等.

如果我们能将上述问题发生时线程的堆栈信息捕捉下来, 那么就能快速定位到问题, 从而问题迎刃而解. 所以, iOS卡顿检查的思路就是创建一个子线程, 监控主线程Runloop的执行, 观察执行耗时是否超过预阈值, 如果有就立即记录线程堆栈.

很有名的PLCrashReporter, 拿来主义就好!

这里也写了一个监测主线程RunLoop的demo

如何判断主线程是否发生了卡顿?

FPS降低

CPU占用率很高

主线程Runloop执行了很久

FPS能够兼容后面两个特征, 但在实际操作过程中发现FPS不好衡量抖动比较大. 对于抢锁或者大量IO的情况, 光靠CPU是不行的, 所以一般检测判断, CPU占用是否超过了100%, 主线程Runloop执行是够超过阈值.

监控FPS(意思是每秒传输帧数,FPS值越低就越卡顿)

所以这个值在一定程度上可以衡量应用在图像绘制渲染处理时的性能。iOS系统中正常的屏幕刷新率为60Hz(60次每秒)。
通过CADisplayLink实现FPS监控,CADisplayLink可以以屏幕刷新的频率调用指定selector,也就是说每次屏幕刷新的时候就调用selector,那么只要在selector方法里面统计每秒这个方法执行的次数,通过次数/时间就可以得出当前屏幕的刷新率了。
可通过YYFPSLabel或者KMCGeigerCounter进行监控,但是前者比较轻量级。(YYFPSLabel)

通过RunLoop监控检查卡顿

通过RunLoop知道主线程上都调用了哪些方法,通过监听 nsrunloop 的状态,知道调用方法是否执行时间过长,从而判断出是否卡顿。
RunLoop 原理来监控卡顿的话,就是要关注这两个阶段。RunLoop 在进入睡眠之前和唤醒后的两个 loop状态定义的值分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting,也就是要触发 Sources回调和接收mach_port消息两个状态。

一个runloop用来管理一个线程 也就是这个线程持续了多久.

  • 1.需要创建一个CFRunLoopObserverContext观察者,然后将观察者runLoopObserver添加到主线程 RunLoop的common模式下观察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
  • 2.创建一个持续的子线程专门用来监控主线程的RunLoop状态

// 创建子线程监控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 子线程开启一个持续的 loop 用来进行监控
    while (YES) {
        long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
        if (semaphoreWait != 0) {
            if (!runLoopObserver) {
                timeoutCount = 0;  // 超时次数
                dispatchSemaphore = 0; // dispatch_semaphore_t 信号量
                runLoopActivity = 0;  // CFRunLoopActivity RunLoop原始状态kCFRunLoopEntry
                return;
            }
            //kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting这两个状态能够检测到是否卡顿
            if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                // 将堆栈信息上报服务器的代码放到这里,包括timeoutCount操作
            } 
        }
        timeoutCount = 0;
    }
});

NSEC_PER_SEC代表的是触发卡顿的时间阈值,单位是秒。可以看到,我们把这个阀值设置成了 3 秒。

获取卡顿的方法堆栈信息

方法1:直接调用系统函数方法的主要思路是:用 signal 进行错误信息的获取
性能消耗小。但是,它只能够获取简单的信息,也没有办法配合 dSYM 来获取具体是哪行代码出了问题,而且能够获取的信息类型也有限。适用于观察大盘统计卡顿情况、而不是想要找到卡顿原因的场景。

方法2:利用PLCrashReporter

能够定位到问题代码的具体位置,而且性能消耗也不大

NSData *lagData = [[[PLCrashReporter alloc]
                                          initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport];
PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL];
NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"lag happen, detail below: \n %@",lagReportString);

明确优化方向

在进行优化之前,我们需要明确优化的方向。是什么影响了我们的APP的启动时间?切忌挖空心思的研究优化main()函数调用之前的占用时间,反而忽略了-applicationDidFinishLaunching:withOptions:函数之后那一堆堆臃肿的网络请求以及业务流程。

我们这里只考虑冷启动的优化,因为冷启动包括了热启动冷启动需要做额外的初始化工作,所以相较而言更慢,导致需要更长的启动等待时间。所以我们先来看看-applicationDidFinishLaunching:withOptions:函数之后,我们的APP都做了哪些事情。

首先会初始化window,加载tabbar,加载首页controller以及数据,可能我们还有一个loading广告页,还有各种各样的业务需求,网络请求。所以这些都是需要去排查的地方,可以尝试通过添加打印时间戳的方式,来测量每个阶段的耗时情况。我们根据排查结果来明确造成启动缓慢的原因。

我们再简单看看main()函数调用之前都发生了什么。

动态链接器 dyld开始将程序依赖的动态链接库递归加载进内存(有缓存机制,第二次启动时会快一些),交由ImageLoader读取所有的类、方法等各种符号,加载完毕后dyld通知runtime调用map_images,遍历所有Class,按继承层次依次调用Class+load方法和其Category+load方法。待所有初始化工作结束后,dyld调用main()函数。

我们可以通过在 Xcode 中 Edit scheme -> Run -> Auguments 将环境变量 DYLD_PRINT_STATISTICS 设为 1,在控制台看到main()函数之前的启动时间。

分解优化目标 分步达到优化目的

  • 如果有很多业务模块在启动时,都在异步的抢占主线程渲染UI,这就会发生阻塞的现象,如果手机性能较好则看不出差异,如果手机性能较差,会卡的想骂人。

  • 如果启动流程依赖网络请求回来才能继续,那么需要考虑网络极差情况下的启动速度。

  • 如果APP有loading广告页并且对分辨率的要求较高,请尝试做缓存吧。

  • 请尽量精简启动流程的各个阶段和逻辑。

  • 对于tabBarController以及主页面Controller中的viewDidLoad和viewWillAppear方法中尽量少做事情。

  • 如果项目历史悠久,请尽快排查清理项目中未使用到的类库以及Framework。

  • 如果时间允许,请删减合并一些OC类,删减没有用到或者可以不用的静态变量、方法等。

尽量轻量化+load方法中的内容,可延迟到+initialize中。

1.从启动 main()之前

2.main()之后 到didFinishLaunchingWithOptions 之前

3.以及didFinishLaunchingWithOptions 作用域结束, 首屏渲染结束

这里我说明一下:

App 启动 分为<冷启动>和<热启动>

冷启动: 是App没有打开.没有做任何准备工作没有进行应用的加载以及构建的时候启动到首屏渲染完成的时间.(我们的App 在这个阶段分为两种首次安装打开以及App不在后台挂起的情况下kill 之后重新打开, 因为做了不同配置导致耗时不同).

热启动: 是你的App已经运行但是是在后台挂起的状态.热启动的时候的时间38ms 个人觉得我们主要优化的应该是冷启动的启动时间

冷启动的优化主要从以下考虑:a. dyld     b. runtime    c. main   (下方图片 从左到右)

1.dyld (动态连接器) :

内核加载主程序,dyld装载Mach-O文件 管理iamges 加载动态库(以后对dyld 在进行深入了解)

装载App可执行文件,递归(动态库依赖于其他动态库)加载所有依赖的动态库;

dyld 把可执行文件以及动态库都装载完之后,通知Runtime进行下一步处理;

2.Runtime (初始化OC结构) :

(这个过程可以将可执行文件和动态库中的所有的符号Class, Protocol, Selecor, IMP等按格式成功加载到内存      中且Runtime对其进行管理,面向对象的相关内容都是Runtime 做的)

1.dyld 方面  :

减少Objc类, 分类的数量, 减少Selector数量 (定期清理不必要的类, 分类). 在dyld 加载Mach-O文件的时候会对类以及分类 进行加载所以减少这些东西可以减少开销

通过分析可以从减少动态库的个数, 合并一些动态库 (定期清理不必要的动态库). 在dyld加载动态库的时候Load dylibs的时候,会分析应用依赖的dylibs.一般情况下iOS 会加载100-400个dylibs,大部分是系统库,针对系统级别的动态库都是经过系统高度优化的(之前我学习底层班的时候进行过学习).而我们自己集成到App的动态库是消耗加载时间的,所以要尽量不要使用内嵌的dylib 这种加载的性能开销最大

所以尽量把多个内嵌dylib 合并成一个来加载或者用static archive(静态库)

减少C++虚函数数量 (多维护虚表)

Swift 尽量使用struct(不用类)

对于dyld 的优化具体方向在dyld 的文章下进行分析(篇幅太长 涉及到dyld 中对缓存Rebase 以及Binding的操作)

3.main 方面  :

在不影响用户体验的前提下, 尽可能将一些操作延迟, 不要全部放到finishLaunching:方法中.重点说明一下几点:

流程减少, 懒加载 放后台初始化 延时初始化 不用的代码删掉;

优化逻辑代码 没用的逻辑该删删

多线程初始化

使用代码不用xib 或者storyboard 进行UI 框架的搭建(尤其是主框架, xib 和 storyboard 也要解析成代码 浪费时间). 对于main部分的优化我项目中的初始化大部分都不是必须的包括token获取等所以基本都是延迟处理的.当然部分也可以交给子线程去做更好.(除了设计到主线程部分的内容或者用到UIKit 的部分). 对于多线程初始化的部分我建议大家仔细读一下这位前辈的探究很有意义对比了各个系统之间的 各个机型之间的 各种处理器之间的不同表现(重点是后半部分我受益匪浅): iOS 启动优化

iOS 页面的卡顿的原因以及如何解决. 如何优化app的启动速度相关推荐

  1. 页面卡顿的原因及排查

    页面卡顿的原因.排查及解决方案 一.渲染不及时,页面掉帧 1>网络请求太多,请求返回的数据比较慢 接口返回慢的话,后端做些优化:前端适当做些缓存,减少不必要的重复的请求 可以从调试工具中的Net ...

  2. 风暴英雄出现服务器未响应,《风暴英雄》游戏卡顿的原因分析及解决办法

    <风暴英雄>游戏卡顿的原因分析及解决办法 2014-11-02 15:14:47来源:游戏下载编辑:阿狸桃子评论(0) <风暴英雄>内测一周以来,有不少玩家反映玩起来比较卡.那 ...

  3. 视频融合云平台视频播放卡顿的原因分析及解决办法

    SkeyeVSS视频融合云平台可在复杂的网络环境中,将分散的各类视频资源进行统一汇聚.整合.集中管理,平台支持多类型设备.多协议方式接入,包括主流标准协议国标GB/T28181.RTMP.RTSP/O ...

  4. iOS之性能优化·优化App的启动速度

    抛砖引玉 启动是 App 给用户的第一印象,启动越慢用户流失的概率就越高,良好的启动速度是用户体验不可缺少的一环. 苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年 ...

  5. js初化加载页面时ajax会调用两次的原因_在前端开发中,有哪些因素会导致页面卡顿

    前端开发不像后端那样,很少出现有大量算法的场景,但是前端性能也是需要优化的.好的代码是保证网页平稳高性能运行的基础,结合以往开发中遇到的场景,本文对前端网页卡顿的原因进行了梳理和分析,并给出了对应的解 ...

  6. ipad更新9.0系统更新服务器,iPhone/iPad升级iOS9出现卡顿的原因及解决办法

    iOS 9正式版目前已经正式推送了,不过在一部分热火朝天升级的时候,有一部分人还在处在观望的状态.现在有很多升级之后的用户反馈说自己的iPhone或者iPad等苹果设备在升级到iOS 9系统会出现暂时 ...

  7. 开发苹果手机 APP,如何保持iOS页面流畅技巧

    iPhone上面的应用一直都是以流畅的操作体验而著称,但是由于之前开发人员把注意力更多的放在开发功能上面,比较少去考虑性能的问题,可能这其中涉及到objective-c,c++跟lua,优化起来相对复 ...

  8. 聊聊使用 JavaScript 做动画出现卡顿的原因

    前段时间在使用 JavaScript 做动画的时候发现做出来的动画会出现卡顿的现象,今天我们主要就来聊一下卡顿的原因以及如何解决这个问题. 使用定时器实现动画出现卡顿的原因 主要原因是浏览器无法确定定 ...

  9. H5代码正常但在IOS端出现页面卡顿

    最近的一次H5项目中,同一套逻辑安卓端运行正常,ios端出现点击后 页面跳转卡顿 接口获取数据后,显示延迟 原因: ios端/安卓端实现H5展示的工具并不相同,ios端工具对console.log的支 ...

最新文章

  1. tomcat线程循环异常终止_腾讯面试官:如何停止一个正在运行的线程?我一脸蒙蔽。。。...
  2. 【机器学习基础】深入理解Logistic Loss与回归树
  3. MyEclipse、eclipse代码自动补全
  4. 6.Vue Class 与 Style 绑定
  5. python问题解决方案_Python安装、遇到的问题及解决方案,python,和,方法
  6. 游族网络回应被新浪财团收购:有相关计划 但对方身份尚不知情
  7. 郭卓惺:互动课堂的搭建实例及相关领域应用
  8. java面试要点---Hibernate面试系统知识点复习,hibernate原理,缓冲---随时更新
  9. This is Huge! PhysBAM code is going to be released?
  10. ACM-ICPC 2018 南京赛区网络预赛Sum,线性筛处理积性函数
  11. Java基础篇之什么是CharArrayWriter
  12. index.php文件分析,OpenCart index.php分析
  13. vue插件开发练习--实用弹窗
  14. 手把手教您怎么编写第一个单片机程序
  15. linux输入法图标不见了,桌面上右下角的输入法图标不见了 怎么找回?
  16. 用JS生成声音,实现钢琴演奏
  17. 微信小程序 分包预加载
  18. 八皇后问题(回溯问题)
  19. fastdfs安装部署整理
  20. html 页面自动滚动,打开网页后屏幕自动滚动代码

热门文章

  1. Win7与Win10在局域网内共享打印机
  2. 别让你20多岁的活法,毁掉你30岁后的人生
  3. 百家号自媒体如何提升文章质量,百家号怎么写好文章,百家号写文章技巧
  4. 你知道小米手机便签如何导入录音和视频文件吗?
  5. 【机器学习课程】第一章机器学习概述 2.机器学习
  6. 敏捷开发“松结对编程”系列之十一:L型代码结构(团队篇之一)
  7. 中维带你揭秘倾斜摄影三维实景
  8. 存储公司芯天下IPO上市申请获受理---义嘉泰骄傲的合作伙伴
  9. UVA 11478 Halum(用bellman-ford解差分约束)
  10. idea中如何生成程序运行的时序图