0%

性能优化

入门级

  1. 用 ARC 管理内存

  2. 在正确的地方使用 reuseIdentifier

  3. 尽量把 views 设置为透明

  4. 避免过于庞大的 XIB

  5. 不要阻塞主线程

  6. 在 ImageViews 中调整图片大小。如果要在 UIImageView 中显示一个来自 bundle 的图片,你应保证图片 的大小和 UIImageView 的大小相同。在运行中缩放图片是很耗费资源的,特别是 UIImageView 嵌套在 UIScrollView 中的情况下。如果图片是从远端服务加载的你不能控制图片大小,比如在下载前调整到合适大 小的话,你可以在下载完成后,最好是用 background thread,缩放一次,然后在 UIImageView 中使用缩放后的图片。

  7. 选择正确的 Collection。

    • Arrays: 有序的一组值。使用 index 来 lookup 很快,使用 value lookup 很慢, 插入/删除很慢。
    • Dictionaries: 存储键值对。 用键来查找比较快。
    • Sets: 无序的一组值。用值来查找很快,插入/删除很快。
  8. 打开 gzip 压缩。app 可能大量依赖于服务器资源,问题是我们的目标是移动设备,因此你就不能指望网 络状况有多好。减小文档的一个方式就是在服务端和你的 app 中打开 gzip。这对于文字这种能有更高压缩 率的数据来说会有更显著的效用。
    iOS 已经在 NSURLConnection 中默认支持了 gzip 压缩,当然 AFNetworking 这些基于它的框架亦然。

中级

  1. 重用和延迟加载(lazy load) Views

    • 更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多view在 UIScrollView 里边的 app 更是如此。
    • 这里我们用到的技巧就是模仿UITableView和UICollectionView的操作:不要一次创建所有的subview, 而是当需要时才创建,当它们完成了使命,把他们放进一个可重用的队列中。这样的话你就只需要在 滚动发生时创建你的 views,避免了不划算的内存分配。
  2. Cache, Cache, 还是 Cache!

    • 一个极好的原则就是,缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。
    • 我们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。
    • NSCache 和 NSDictionary 类似,不同的是系统回收内存的时候它会自动删掉它的内容。
  3. 权衡渲染方法.性能能还是要 bundle 保持合适的大小。

  4. 处理内存警告.移除对缓存,图片 object 和其他一些可以重创建的 objects 的 strong references.

  5. 重用大开销对象

  6. 一些 objects 的初始化很慢,比如 NSDateFormatter 和 NSCalendar。然而,你又不可避免地需要使用它们, 比如从 JSON 或者 XML 中解析数据。想要避免使用这个对象的瓶颈你就需要重用他们,可以通过添加属性 到你的 class 里或者创建静态变量来实现。

  7. 避免反复处理数据.在服务器端和客户端使用相同的数据结构很重要。

  8. 选择正确的数据格式.解析 JSON 会比 XML 更快一些,JSON 也通常更小更便于传输。从 iOS5 起有了官方 内建的 JSON deserialization 就更加方便使用了。但是 XML 也有 XML 的好处,比如使用 SAX 来解析 XML 就 像解析本地文件一样,你不需像解析 json 一样等到整个文档下载完成才开始解析。当你处理很大的数据的 时候就会极大地减低内存消耗和增加性能。

  9. 正确设定背景图片

    • 全屏背景图,在view中添加一个UIImageView作为一个子View
    • 只是某个小的view的背景图,你就需要用UIColor的colorWithPatternImage来做了,它会更快地渲染也不会花费很多内存:
  10. 减少使用 Web 特性。想要更高的性能你就要调整下你的 HTML 了。第一件要做的事就是尽可能移除不 必要的 javascript,避免使用过大的框架。能只用原生 js 就更好了。尽可能异步加载例如用户行为统计 script 这种不影响页面表达的 javascript。注意你使用的图片,保证图片的符合你使用的大小。

  11. Shadow Path 。CoreAnimation 不得不先在后台得出你的图形并加好阴影然后才渲染,这开销是很大的。 使用 shadowPath 的话就避免了这个问题。使用 shadow path 的话 iOS 就不必每次都计算如何渲染,它使用 一个预先计算好的路径。但问题是自己计算 path 的话可能在某些 View 中比较困难,且每当 view 的 frame 变化的时候你都需要去 update shadow path.

  12. 优化 Table View

    • 正确使用reuseIdentifier来重用cells
    • 尽量使所有的viewopaque,包括cell自身
    • 避免渐变,图片缩放,后台选人
    • 缓存行高
    • 如果cell内现实的内容来自web,使用异步加载,缓存请求结果
    • 使用shadowPath来画阴影
    • 减少subviews的数量
    • 尽量不适用cellForRowAtIndexPath:,如果你需要用到它,只用-一次然后缓存结果
    • 使用正确的数据结构来存储数据
    • 使用 rowHeight, sectionFooterHeight 和 sectionHeaderHeight 来设定固定的高,不要请求 delegate
  13. 选择正确的数据存储选项

    • 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了。

高级

  1. 加速启动时间。快速打开 app 是很重要的,特别是用户第一次打开它时,对 app 来讲,第一印象太太太 重要了。你能做的就是使它尽可能做更多的异步任务,比如加载远端或者数据库数据,解析数据。避免过 于庞大的 XIB,因为他们是在主线程上加载的。所以尽量使用没有这个问题的 Storyboards 吧!一定要把设 备从 Xcode 断开来测试启动速度
  2. 使用 Autorelease Pool。NSAutoreleasePool`负责释放 block 中的 autoreleased objects。一般情况下它会自 动被 UIKit 调用。但是有些状况下你也需要手动去创建它。假如你创建很多临时对象,你会发现内存一直在 减少直到这些对象被 release 的时候。这是因为只有当 UIKit 用光了 autorelease pool 的时候 memory 才会被 释放。消息是你可以在你自己的@autoreleasepool 里创建临时的对象来避免这个行为。
  3. 选择是否缓存图片。常见的从 bundle 中加载图片的方式有两种,一个是用 imageNamed,二是用 imageWithContentsOfFile,第一种比较常见一点。
  4. 避免日期格式转换。如果你要用 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 就不会产生离屏渲染
希望对您有所帮助,您的支持将是我莫大的动力!