前言: 随着项目的扩大和功能的增多,代码没有经过严格的调试和优化,要么任性地卡顿运行,要么就低调地崩溃,最后导致用户用着不开心,开发者也比较烦恼。
为了突破这个这个关卡其实并不难,首先开发者只要在Xcode自带的监控调试工具 Instruments 上花点功夫就能够让代码顺畅运行。Instruments 提供了很多检测功能,重点介绍一下我常用的几大类:
Analyze—静态分析
Leaks—内存泄露(动态内存泄露检测)
Allocations:监测内存使用 / 分配情况
Time Profiler:检测分析代码的执行时间。
(一)第一波基本概念
1.1.内存空间的划分: 我们知道,一个进程占用的内存空间,包含5种不同的数据区:(1)BSS段:通常是存放未初始化的全局变量;(2)数据段:通常是存放已初始化的全局变量。(3)代码段:通常是存放程序执行代码。(4)堆:通常是用于存放进程运行中被动态分配的内存段 , OC对象(所有继承自NSObject的对象)就存放在堆里。(5)栈:由编译器自动分配释放,存放函数的参数值,局部变量等值。
栈内存是系统来管理的,因此我们常说的内存管理,指的是堆内存的管理,也就是所有OC对象的创建和销毁的管理。伴随着iOS5的到来,苹果推出了ARC(自动引用计数)技术,此模式下编译器会自动在合适的地方插入retain、release、autorelease语句,也就是说编译器会自动生成内存管理的代码,解放了广大程序猿的双手,也基本上避免了内存泄露问题,但是呢...
1.2.内存泄露: 百度百科给的定义是:"内存泄漏也称作'存储渗漏',用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏"。在iOS应用中的内存泄露,原因一般有循环引用、错用Strong/copy等。
(二)第二波( Instruments多功能检测)
2.1. Analyze—静态分析
顾名思义,静态分析不需要运行程序,就能检查到存在内存泄露的地方。
1. 使用方法:打开Xcode,command + shift + B;或者Xcode - Product - Analyze;
2. 常见的三种泄露情形:
(1)创建了一个对象,但是并没有使用。Xcode提示信息: Value Stored to 'number' is never read 。翻译一下:存储在'number'里的值从未被读取过。
(2)创建了一个(指针可变的)对象,且初始化了,但是初始化的值一直没读取过。Xcode提示信息: Value Stored to 'str' during its initialization is never read
(3)调用了让某个对象引用计数加1的函数,但没有调用相应让其引用计数减1的函数。Xcode提示信息: Potential leak of an object stored into 'subImageRef' 。 翻译一下:subImageRef对象的内存单元有潜在的泄露风险。
2.2: Leaks—内存泄露
Leaks是动态的内存泄露检查工具,需要一边运行程序,一边检测。
1.使用方法: 进入Xcode,command + control + i ;或者Xcode - Xcode - Open Developer Tool - Instruments; 或者Xcode - Product - Profile。选择Leaks。
一般用静态分析检查过的代码,内存泄露都比较少。测试了有3个项目能点的按钮都点了,能进的页面都进的,Leaks也没检测到泄露。
2.3. Allocations—内存分配
Allocations是检测程序运行过程中的内存分配情况的,也需要同时运行着程序。
1.打开方法:同上。
2.界面情况如下:
2.4:Time Profiler 主要作用:分析代码的执行时间,以便找出导致程序变慢的原因。
当点击Time Profiler应用程序开始运行后.就能获取到整个应用程序运行消耗时间分布和百分比.为了保证数据分析在统一使用场景真实行有如下点需要注意:
在开始进行应用程序性能分析前,请一定要使用真机,因为模拟器运行在Mac上,然而Mac上的CPU往往比iOS设备要快。相反,Mac上的GPU和iOS设备的完全不一样,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操作在模拟器上运行的更慢,尤其是使用CAEAGLLayer来写一些OpenGL的代码时候. 这就导致模拟器性能数据和用户真机使用性能数据相去甚运.
另外在开始性能分析前,另外一件很重要的事情是,应用程序一定要运行在Distribution 而不是Debug模式下.
在发布环境打包的时候,编译器会引入一系列提高性能的优化,例如去掉调试符号或者移除并重新组织代码.另iOS引入一种Watch Dog[看门狗]机制.不同的场景下,“看门狗”会监测应用的性能。如果超出了该场景所规定的运行时间,“看门狗”就会强制终结这个应用的进程.开发者可以crashlog看到对应的日志.但Xcode在调试配置下会禁用Watch Dog.
如图所示
相关配置项如下
Separate by Thread: 每个线程应该分开考虑。只有这样你才能揪出那些大量占用CPU的"重"线程
Invert Call Tree: 从上倒下跟踪堆栈,这意味着你看到的表中的方法,将已从第0帧开始取样,这通常你是想要的,只有这样你才能看到CPU中话费时间最深的方法.也就是说FuncA{FunB{FunC}} 勾选此项后堆栈以C->B-A 把调用层级最深的C显示在最外面
Hide Missing Symbols: 如果dSYM无法找到你的app或者系统框架的话,那么表中看不到方法名只能看到十六进制的数值,如果勾线此项可以隐藏这些符号,便于简化数据
Hide System Libraries: 勾选此项你会显示你app的代码,这是非常有用的. 因为通常你只关心cpu花在自己代码上的时间不是系统上的
Show Obj-C Only: 只显示oc代码 ,如果你的程序是像OpenGl这样的程序,不要勾选侧向因为他有可能是C++的
Flatten Recursion: 递归函数, 每个堆栈跟踪一个条目
Top Functions: 一个函数花费的时间直接在该函数中的总和,以及在函数调用该函数所花费的时间的总时间。
因此,如果函数A调用B,那么A的时间报告在A花费的时间加上B.花费的时间,这非常有用,因为它可以让你每次下到调用堆栈时挑最大的时间数字,归零在你最耗时的方法。如上做了相关配置之后会显示如下图所示情形
(三)第三波(小结)
除了以上所描述常用instruments外,下面一些偶尔也会用到
Core Data:监测读取、缓存未命中、保存等操作,能直观显示是否保存次数远超实际需要。
Cocoa Layout:观察约束变化,找出布局代码的问题所在。 Network:跟踪 TCP / IP 和 UDP / IP 连接。
Automations:创建和编辑测试脚本来自动化 iOS 应用的用户界面测试。
最后instruments只是一组工具,帮助我们分析代码的工具,可能检查的出的内存问题和性能问题,肯定还是由代码造成的。问题的本质还是养成良好的代码习惯,才是根本的解决方法。
首先是避免出现静态分析里提到的三种常见内存泄露问题,测试的好几个项目里,都有出现类似的问题。在此有以下几点tips:
tip1:哪些情况会增加App的内存占用?(开发中未完成的项目)
(1) 创建对象,定义变量。
(2)调用一个函数或方法。
tip2:哪些情况会增加CPU的消耗?(bireme)
(1) 创建对象、调整对象属性、销毁对象。
(2)布局计算和Autolayout。
( 3)文本的计算和渲染。
( 4)图片的解码和绘制。 (用Time Profiler分析一下,可以更直观地感受到哪些操作比较耗时,使用方法同上。)
tip3:
做好cell等可复用对象的重用;可以只创建一次的对象,不要创建多次(比如页面的某个功能弹窗); 用较少的对象和方法调用去实现功能;将耗时的操作放在子线程等可以对内存和性能做一些优化 。