1. 使用ARC进行内存管理
ARC除了能避免内存泄露外,还有助于程序性能的提升
2. 在适当的情况下使用reuseIdentifier
为了使用reuseIdentif iers,在tableview请求一个新的cell时,在数据源中调用下面的方法:
staticNSString*CellIdentifier=@"Cell";
UITableViewCell*cell=[tableViewdequeueReusableCellWithIdentifier:CellIdentifier f orIndexPat h:indexPat h];
3. 尽可能将View设置为不透明(Opaque)
opaque属性提示绘制系统如何处理view。如果opaque设置为YES,绘图系统会将view看为完全不透明,这样绘图系统就可以 优化一些绘制操作以提升性能。如果设置为NO,那么绘图系统结合其它内容来处理view。默认情况下,这个属性 是YES。
如果屏幕是静止的,那么这个opaque属性的设置与否不是一个大问题,但是,如果view是嵌入到scroll view中 的,或者是复杂动画的一部分,不设置这个属性的话肯定会影响程序的性能!
4. 避免臃肿的XIBs
如果必须要使用XIBs 的话,尽量让XIBs 文件简单。并且每个view controller对于一个XIB文件,如果可以的话,把一个view controller的view不同的层次单独分到一个XIBs文件中。
当把一个XIB文件加载到内存时,XIB文件中的所有内容都 将被加载到内存中,包括图片。如果有一个view还不立即使用的 话,就会造成内存的浪费。
5. 不要阻塞主线程
永远都不要在主线程做繁重的任务。因为UIKit 的任务都在主线程中进 行,例如绘制、触摸管理和输入响应。如果你的代码阻塞了主线程,那么程 序将出现反应迟钝。在执行I/O操作中,大多数情况下都会阻塞主线程,这些操作需要从外部资源读写,例如磁盘或者网络。
6. 让图片的大小跟UIImageView一样
如果需要将程序bundle中的图片显示到UIImageView中,请确保图 片和UIImageView的大小是一样的。因为图片的缩放非常耗费资 源,特别是将UIImageView嵌入到UIScro llView中。
如果是从远程服务中下载图片,有时候你控制不了图片的尺寸, 或者在下载之前无法在服务器上进行图片的缩放。这种情况,当 图片下载完之后,你可以手动进行图片的缩放,最好是在后台线程中,然后再在UIImageView中使用缩放过的图片。
7. 选择正确的集合
数组:是一个值按顺序排列的一个列表。根据索引可以快速查找,不过根据值进行查找就比较慢,另外插入和删除也比较慢。
字典: 存储键/值对。根据键可以快速查找。
Sets: 是一个值无序排列的列表,根据值可以快速查找,另外插入和删除也比较快。
8. 使用GZIP压缩
越来越多的程序依赖于外部数据,这些数据一般来自远程服务器或者其它的外部APIs 。使用GZIP对网络传输中的数据进行压缩,这样可以减小文件的大小,并加快下载的速度。压缩对于文本数据特别有用,因为文本具有很高的压缩比。
9. 重用和延迟加载View
程序界面中包含更多的view,意味着界面在显示的时候,需要进行更多的绘制任务,也就意味着需要消耗更多的CPU和内存资源。特别是在一个UIScro llView里面加入了许多view。 这种情况的管理技巧可以参考UITableView和UICollectionView的行为:不要一次性创建所有的subview,而是在需要的时候在创建view,并且当view使用完毕时候将它们添加到重用队列中。这样就可以仅在UIScrollView滚动的时候才配置view,以此可以避免分配创建view带来的成本。
现在有这样的一个问题:在程序中需要显示的view在什么时机创建(比如说,当用户点击某个按钮,需要显示某个view)。这里有两种可选方法:
1. 在屏幕第一次加载以及隐藏的时候,创建view;然后在需要的时候,再把view显示出来。
2. 直到需要显示view的时候,才创建并显示view。 每种方法都有各自的优点和确定。
使用第一种方法,需要消耗更多的内容,因为创建出来的view一直占据着内存,直到view被release掉。不过,使用这种方法,当用户点击按钮时,程序会很快的显示出view,因为只需要修改一下view的可见性即可。
而使用第二种方法则产生相反的效果,当需要的时候才创建view,这会消耗更少的内存,不过,当用户点击按钮的时候,不会立即显示出view。
10. 学会利用缓存
在开发程序时,一个重要的规则就是“缓存重要的内容”——这些内容一般不会改变,并且访问的频率比较高。远程服务器的响应内容,图片,甚至是计算结果,比如UITableView的行高都可用缓存
11. 考虑绘制
在iOS中制作漂亮的按钮有多种方法。可以使用全尺寸图片,可缩放图片,或者使用CALayer, CoreGraphics, 甚至是OpenGL来手 动测量和绘制按钮。
使用预渲染图片技术是最快的,因为iOS中不用等到在 屏幕上显示的时候才创建图形和对形状进行绘制(图片已经创建 好了!)。这样带来的问题是需要把所有的图片都放到程序bundle中,从而增加了程序的大小。
12. 处理内存警告
当系统内存偏低时,iOS会通知所有在运行的程序。
UIKit中提供了如下几种方法来接收低内存(low-memory)警告:
(1)实现app delegate中的applicationDidReceiveMemoryWarning: 方法。
(2)在UIViewController子类中重写(Override)didReceiveMemoryWarning方法。
(3)在通知中心里面注册UIApplicationDidReceiveMemoryWarningNotificatio通知。
在收到以上任意的警告时,需要立即释放任何不需要的内存。
例如,UIViewController的默认情况是清除掉当前不可见的view, 在UIViewController的子类中,可以清除一些额外的数据。程序中没有显示在当前屏幕中的图片也可以release掉。
13. 重用花销很大的对象
有些对象的初始化非常慢——比如NSDateFormatter和NSCalendar。不过有时候可以避免使用这些对象,例如在解析JSON/XML中的日期 时。当使用这些对象时,为了避免性能上的瓶颈,可以尝试尽量重用这些 对象——在类中添加一个属性或者创建一个静态变量。
注意,如果使用静态变量的话,对象会在程序运行的时候一直存在, 就像单例一样
下面的代码演示创建一个延迟加载的日期格式属性。第一次调用属性 的时候,会创建一个新的日期格式。之后再调用的话,会返回已经创 建好的实例对象:
// in your .h or inside a class extension
@property (nonatomic, strong) NSDateFormatter *formatter;
//insidetheimplementation(.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
if (! _formatter) {
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
}
return _formatter;
}
另外,还需要记住的是在设置NSDateFormatter的日期格式时,同样跟创建新的一个NSDateFormatter实例对象 时一样慢!因此, 在程序中如果需要频繁的处理日期格式,那么对NSDateFormatter进行重用是非常好的。
14. 避免重新处理数据
许多程序都需要从远程服务器中获取数据,以满足程序的需求。这些数据一般是JSON或XML格式。在请求和接收数据时,使用相同的数据结构非常重要。为什么呢?在内存中把数据转换为适合程序的数据格式是需要付出额外代价的。
例如,如果你需要在tableview中显示一些数据,那么请求和接收的数据格式最好是数组格式的,这样可以避免一 些中间操作——将数据转换为适合程序使用的数据结构。
类似的,如果程序是根据键来访问具体的值,那么最好请求和接收一个键/值对字典。
15. 选择正确的数据格式
将数据从程序传到网络服务器中有多种方法,其中使用的数据格式基本都是JSON和XML。你需要做的就是在程序中选择正确的数据格式。JSON的解析速度非常快,并且要比XML小得多,也就意味着只需要传输更少数据。
16. 设置适当的背景图片
有两种方法来给view设置一个背景图片:
1. 可以使用UIColor的colorWithPatternImge方法来创建一个颜色,并将这个颜色设置为view的背景颜色。
2. 可以给view添加一个UIImageView子视图。
如果你有一个全尺寸的背景图片,那么应该使用UIImageView,因为UIColor的colorWithPatternImge方法是用来创建小图片的——该图片会被重复使用。此时使用UIImageView会节省很多内存。
UIImageView*backgroundView=[[UIImageViewalloc]initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];
不过,如果你计划用小图片当做背景,那么应该使用UIColor的colorWithPatternImge方法。这种情况下绘制速度 会很快,并且不会消耗大量的内存。
self.view.backgroundColor=[UIColorcolorWithPatternImage:[UIImage imageNamed:@"background"]];
17. 降低Web内容的影响
UIWebView非常有用。用它可以很容易的显示web内容,甚至可以构建UIKit 空间难以显示的内容。不过,你可以能已经注意到程序中使用的UIWebView组建没有苹果的Saf ari程序快。这是因为JIT 编译限制了WebKit的Nitro引擎的使用。
因此为了获得更好的性能,需要调整一下HTML的大小。首先就是尽量的摆脱JavaScript,并避免使用大的框架, 例如jQuery。有时候使用原始的JavaScript要比别的框架快。
另外,尽量的异步加载JavaScript文件——特别是不直接影响到页面行为时,例如分析脚本。
最后——让使用到的图片,跟实际需要的一样大小。以此节省内存和提升速度。
18. 设置阴影路径
如果需要在view活layer中添加一个阴影,该如何处理呢? 大多数开发者首先将QuartzCore框架添加到工程中,然后添加如下代码:
1. #import <QuartzCore/QuartzCore.h> 2.
//Somewherelater...
UIView*view=[[UIViewalloc]init];
5.
// Setup the shadow ...
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity=0.6;
然而不幸的是上面这种方法有一个问题。Core Animation在渲染阴影效果之前,必须通过做一个离屏(of f screen)才能确定view的形状,而这个离屏操作非常耗费资源。
下面有一种方法可以更容易的让系统进行阴影渲染:设置阴影路径!
view.layer.shadowPath=[[UIBezierPathbezierPathWithRect:view.bounds]CGPath];
通过设置阴影路径,iOS就不用总是再计算该如何绘制阴影了。只需要使用你预先计算好的路径即可。有一点不好的是,根据view的格式,自己可能很难计算出路径。另外一个问题就是当view的frame改变时,必须每次都更新一下阴影路径。
19. 优化tableView
tableview需要快速的滚动——如果不能的话,用户会感觉到停顿。
为了让table view平滑的滚动,确保遵循了如下建议:
(1)设置正确的reuseIdentifer以重用cell。
(2) 尽量将view设置为不透明,包括cell本身。
(3)避免渐变,图像缩放以及离屏绘制。
(4)如果row的高度不相同,那么将其缓存下来。
(5) 如果cell显示的内容来自网络,那么确保这些内容是通过异步来获取的。
(6)使用shadowPath来设置阴影。
(7)减少subview的数量。
(8)在cellForRowAtIndexPath:中尽量做更少的操作。如果需要做一些处理,那么最好做过一次之后,就将结果缓存起来。
(9)使用适当的数据结构来保存需要的信息。不同的结构会带来不同的操作代价。
(10)使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight 来设置一个恒定高度, 而不要从delegate中获取。
20. 选择正确的数据存储方式
当需要存储和读取大量的数据时,该如何选择存储方式呢? 有如下选择:
(1)使用NSUserDefaults进行存储保存为XML
(2)JSON或Plist 格式的文件
(3)利用NSCoding进行归档存储到一个本地数据库,例如SQLit e。
(4)使用Core Data
虽然NSUserDef aults很好并且容易,不过只只针对于存储小量数据
大量数据保存为结构化的文件也可能会带来问题。一般,在解析这些结构数据之前,需要将内容全部加载到内存 中,这是很消耗资源的。虽然可以使用SAX来处理XML文件,但是这有点复杂。另外,加载到内存中的所有对象, 不一定全部都需要用到。
那么使用NSCoding来保存大量数据怎么样呢? 因为它同样是对文件进行读写,因此依然存在上面说的问题。
要保存大量的数据,最好使用SQLite或Core Data。通过SQLite或Core Data可以进行具体的查询——只需要获取
并加载需要的数据对象——避免对数据进行不合理的搜索。在性能方面,SQLite和Core Data差不大。
SQLite和Core Data最大的区别实际上就是用法上。Core Data代表一个对象模型,而SQLite只是一个DBMS。
一般,苹果建议使用Core Data,不过如果你有特殊的原因不能使用Core Data的话,可以使用低级别的SQLite。
在程序中,如果选择使用SQLite,这里有个方便的库FMDB :可以利用该库操作SQLite数据库,而不用深入使用SQLite C API。
21. 加速启动时间
(1)让程序尽量快速启动的方法就是尽量以异步方式执行任务,例如网络请求,数据访问或解析。
(2)另外,避免使用臃肿的XIBs,因为XIB的加载是在主线程中进行的。
22. 尽量避免Date格式化
如果有许多日期需要使用NSDateFormatter,那么需要小心对待了。如之前(重用花销很大的对象)所提到的,
无论什么时候,都应该尽量重用NSDateFormatters。
然而,如果你需要更快的速度,那么应该使用C来直接解析日期,而不NSDateFormatter。Sam Sof fes写了一 篇文章,其中提供了一些解析ISO- 8601格式日期字符的串代码。你只需要简单的调整一下其中的代码就可以满足自己特殊的需求了。
如果你自己能控制处理日期的格式,那么可以选择 Unix timestamps。Unix timestamps是一个简单的整数,代表 了从新纪元时间(epoch)开始到现在已经过了多少秒,通常这个新纪元参考时间是00:00:00 UTC on 1 January 1970。
你可以很容易的见这个时间戳转换为NSDat e,如下所示:
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
ret urn [NSDat e dat eWit hTimeInt ervalSince1970:t imest amp];
}
上面这个方法比C函数还要快! 注意:许多网络APIs返回的时间戳都是毫秒,因此需要注意的是在将这个时间戳传递给dateFromUnixTimestamp之前需要除以1000。
关于iOS 启动性能优化可参考: https://mp.weixin.qq.com/s/Kf3EbDIUuf0aWVT-UCEmbA