入门级
用 ARC 管理内存
在正确的地方使用 reuseIdentifier
尽量把 views 设置为透明
避免过于庞大的 XIB
不要阻塞主线程
在 ImageViews 中调整图片大小。如果要在 UIImageView 中显示一个来自 bundle 的图片,你应保证图片 的大小和 UIImageView 的大小相同。在运行中缩放图片是很耗费资源的,特别是 UIImageView 嵌套在 UIScrollView 中的情况下。如果图片是从远端服务加载的你不能控制图片大小,比如在下载前调整到合适大 小的话,你可以在下载完成后,最好是用 background thread,缩放一次,然后在 UIImageView 中使用缩放后的图片。
选择正确的 Collection。
- Arrays: 有序的一组值。使用 index 来 lookup 很快,使用 value lookup 很慢, 插入/删除很慢。
- Dictionaries: 存储键值对。 用键来查找比较快。
- Sets: 无序的一组值。用值来查找很快,插入/删除很快。
打开 gzip 压缩。app 可能大量依赖于服务器资源,问题是我们的目标是移动设备,因此你就不能指望网 络状况有多好。减小文档的一个方式就是在服务端和你的 app 中打开 gzip。这对于文字这种能有更高压缩 率的数据来说会有更显著的效用。
iOS 已经在 NSURLConnection 中默认支持了 gzip 压缩,当然 AFNetworking 这些基于它的框架亦然。
中级
重用和延迟加载(lazy load) Views
- 更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多view在 UIScrollView 里边的 app 更是如此。
- 这里我们用到的技巧就是模仿UITableView和UICollectionView的操作:不要一次创建所有的subview, 而是当需要时才创建,当它们完成了使命,把他们放进一个可重用的队列中。这样的话你就只需要在 滚动发生时创建你的 views,避免了不划算的内存分配。
Cache, Cache, 还是 Cache!
- 一个极好的原则就是,缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。
- 我们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。
- NSCache 和 NSDictionary 类似,不同的是系统回收内存的时候它会自动删掉它的内容。
权衡渲染方法.性能能还是要 bundle 保持合适的大小。
处理内存警告.移除对缓存,图片 object 和其他一些可以重创建的 objects 的 strong references.
重用大开销对象
一些 objects 的初始化很慢,比如 NSDateFormatter 和 NSCalendar。然而,你又不可避免地需要使用它们, 比如从 JSON 或者 XML 中解析数据。想要避免使用这个对象的瓶颈你就需要重用他们,可以通过添加属性 到你的 class 里或者创建静态变量来实现。
避免反复处理数据.在服务器端和客户端使用相同的数据结构很重要。
选择正确的数据格式.解析 JSON 会比 XML 更快一些,JSON 也通常更小更便于传输。从 iOS5 起有了官方 内建的 JSON deserialization 就更加方便使用了。但是 XML 也有 XML 的好处,比如使用 SAX 来解析 XML 就 像解析本地文件一样,你不需像解析 json 一样等到整个文档下载完成才开始解析。当你处理很大的数据的 时候就会极大地减低内存消耗和增加性能。
正确设定背景图片
- 全屏背景图,在view中添加一个UIImageView作为一个子View
- 只是某个小的view的背景图,你就需要用UIColor的colorWithPatternImage来做了,它会更快地渲染也不会花费很多内存:
减少使用 Web 特性。想要更高的性能你就要调整下你的 HTML 了。第一件要做的事就是尽可能移除不 必要的 javascript,避免使用过大的框架。能只用原生 js 就更好了。尽可能异步加载例如用户行为统计 script 这种不影响页面表达的 javascript。注意你使用的图片,保证图片的符合你使用的大小。
Shadow Path 。CoreAnimation 不得不先在后台得出你的图形并加好阴影然后才渲染,这开销是很大的。 使用 shadowPath 的话就避免了这个问题。使用 shadow path 的话 iOS 就不必每次都计算如何渲染,它使用 一个预先计算好的路径。但问题是自己计算 path 的话可能在某些 View 中比较困难,且每当 view 的 frame 变化的时候你都需要去 update shadow path.
优化 Table View
- 正确使用reuseIdentifier来重用cells
- 尽量使所有的viewopaque,包括cell自身
- 避免渐变,图片缩放,后台选人
- 缓存行高
- 如果cell内现实的内容来自web,使用异步加载,缓存请求结果
- 使用shadowPath来画阴影
- 减少subviews的数量
- 尽量不适用cellForRowAtIndexPath:,如果你需要用到它,只用-一次然后缓存结果
- 使用正确的数据结构来存储数据
- 使用 rowHeight, sectionFooterHeight 和 sectionHeaderHeight 来设定固定的高,不要请求 delegate
选择正确的数据存储选项
- NSUserDefaults 的问题是什么?虽然它很 nice 也很便捷,但是它只适用于小数据,比如一些简单的布 尔型的设置选项,再大点你就要考虑其它方式了
- XML 这种结构化档案呢?总体来说,你需要读取整个文件到内存里去解析,这样是很不经济的。使用 SAX 又是一个很麻烦的事情。
- NSCoding?不幸的是,它也需要读写文件,所以也有以上问题。
- 在这种应用场景下,使用 SQLite 或者 Core Data 比较好。使用这些技术你用特定的查询语句就能只加载你需要的对象。
- 在性能层面来讲,SQLite和CoreData是很相似的。他们的不同在于具体使用方法。
- Core Data 代表一个对象的 graph model,但 SQLite 就是一个 DBMS。
- Apple 在一般情况下建议使用 Core Data,但是如果你有理由不使用它,那么就去使用更加底层的 SQLite吧。
- 如果你使用SQLite,你可以用FMDB这个库来简化SQLite的操作,这样你就不用花很多经历了解SQLite的C API了。
高级
- 加速启动时间。快速打开 app 是很重要的,特别是用户第一次打开它时,对 app 来讲,第一印象太太太 重要了。你能做的就是使它尽可能做更多的异步任务,比如加载远端或者数据库数据,解析数据。避免过 于庞大的 XIB,因为他们是在主线程上加载的。所以尽量使用没有这个问题的 Storyboards 吧!一定要把设 备从 Xcode 断开来测试启动速度
- 使用 Autorelease Pool。NSAutoreleasePool`负责释放 block 中的 autoreleased objects。一般情况下它会自 动被 UIKit 调用。但是有些状况下你也需要手动去创建它。假如你创建很多临时对象,你会发现内存一直在 减少直到这些对象被 release 的时候。这是因为只有当 UIKit 用光了 autorelease pool 的时候 memory 才会被 释放。消息是你可以在你自己的@autoreleasepool 里创建临时的对象来避免这个行为。
- 选择是否缓存图片。常见的从 bundle 中加载图片的方式有两种,一个是用 imageNamed,二是用 imageWithContentsOfFile,第一种比较常见一点。
- 避免日期格式转换。如果你要用 NSDateFormatter 来处理很多日期格式,应该小心以待。就像先前提到 的,任何时候重用 NSDateFormatters 都是一个好的实践。如果你可以控制你所处理的日期格式,尽量选择 Unix 时间戳。你可以方便地从时间戳转换到 NSDate:
如何提升 tableview 的流畅度?
本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
- CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、 图像的绘制
- GPU:纹理的渲染
卡顿优化在 CPU 层面
- 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
- 不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的
修改 - 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
- Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
- 图片的 size 最好刚好跟 UIImageView 的 size 保持一致
- 控制一下线程的最大并发数量
- 尽量把耗时的操作放到子线程
- 文本处理(尺寸计算、绘制) - 图片处理(解码、绘制)
卡顿优化在 GPU 层面
- 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
- GPU 能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以
纹理尽量不要超过这个尺寸 - 尽量减少视图数量和层次
- 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
- 尽量避免出现离屏渲染
1.预排版,提前计算
在接收到服务端返回的数据后,尽量将 CoreText 排版的结果、单个控件的高度、cell 整体的高度提 前计算好,将其存储在模型的属性中。需要使用时,直接从模型中往外取,避免了计算的过程。
尽量少用 UILabel,可以使用 CALayer 。避免使用 AutoLayout 的自动布局技术,采取纯代码的方 式
2.预渲染,提前绘制
例如圆形的图标可以提前在,在接收到网络返回数据时,在后台线程进行处理,直接存储在模型数据里,
回到主线程后直接调用就可以了
避免使用 CALayer 的 Border、corner、shadow、mask 等技术,这些都会触发离屏渲染。
3.异步绘制
4.全局并发线程 5.高效的图片异步加载
如何优化 APP 的电量?
程序的耗电主要在以下四个方面:
- CPU 处理
- 定位
- 网络
- 图像
优化的途径主要体现在以下几个方面:
- 尽可能降低 CPU、GPU 的功耗。
- 尽量少用 定时器。
- 优化 I/O 操作。
o 不要频繁写入小数据,而是积攒到一定数量再写入
o 读写大量的数据可以使用 Dispatch_io ,GCD 内部已经做了优化。 o 数据量比较大时,建议使用数据库 - 网络方面的优化
o 减少压缩网络数据 (XML -> JSON -> ProtoBuf),如果可能建议使用 ProtoBuf。
o 如果请求的返回数据相同,可以使用 NSCache 进行缓存
o 使用断点续传,避免因网络失败后要重新下载。
o 网络不可用的时候,不尝试进行网络请求
o 长时间的网络请求,要提供可以取消的操作
o 采取批量传输。下载视频流的时候,尽量一大块一大块的进行下载,广告可以一次下载
多个 - 定位层面的优化
o 如果只是需要快速确定用户位置,最好用 CLLocationManager 的 requestLocation 方法。 定位完成后,会自动让定位硬件断电
o 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务
o 尽量降低定位精度,比如尽量不要使用精度最高的 kCLLocationAccuracyBest
o 需要后台定位时,尽量设置 pausesLocationUpdatesAutomatically 为 YES,如果用户不太
可能移动的时候系统会自动暂停位置更新
o 尽 量 不 要 使 用 startMonitoringSignificantLocationChanges , 优 先 考 虑
startMonitoringForRegion: - 硬件检测优化
用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、 磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
如何有效降低 APP 包的大小?
可执行文件
- 编译器优化
- Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 设置为 YES
- 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 设置为 NO, Other C Flags 添加 -fno-exceptions
- 利用 AppCode 检测未使用的代码:菜单栏 -> Code -> Inspect Code
- 编写LLVM插件检测出重复代码、未被调用的代码
资源
资源包括 图片、音频、视频 等 - 优化的方式可以对资源进行无损的压缩
- 去除没有用到的资源
什么是 离屏渲染?什么情况下会触发?该如何应对?
离屏渲染出发的场景有以下:
- 圆角(maskToBounds并用才会触发) - 图层蒙版
- 阴影
- 光栅化
为什么要避免离屏渲染?
CPUGPU 在绘制渲染视图时做了大量的工作。离屏渲染发生在 GPU 层面上,会创建新的渲染缓冲区,会 触发 OpenGL 的多通道渲染管线,图形上下文的切换会造成额外的开销,增加 GPU 工作量。如果 CPU GPU 累计耗时 16.67 毫秒还没有完成,就会造成卡顿掉帧。
圆角属性、蒙层遮罩 都会触发离屏渲染。指定了以上属性,标记了它在新的图形上下文中,在未愈合之前, 不可以用于显示的时候就出发了离屏渲染。 - 在OpenGL中,GPU有2种渲染方式
- On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作
- Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
- 离屏渲染消耗性能的原因
- 需要创建新的缓冲区
- 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏 (Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文 环境从离屏切换到当前屏幕
- 哪些操作会触发离屏渲染?
- 光栅化,layer.shouldRasterize = YES
- 遮罩,layer.mask
- 圆角,同时设置layer.masksToBounds=YES、layer.cornerRadius大于0
- 考虑通过 CoreGraphics 绘制裁剪圆角,或者叫美工提供圆角图片
- 阴影,layer.shadowXXX,如果设置了 layer.shadowPath 就不会产生离屏渲染