一、初级:
在开发过程中,下面这些初级技巧需要时刻注意:
1)使用ARC进行内存管理
2)在适当的情况下使用reuseIdentifier
3)尽可能将View设置成不透明
4)避免臃肿的xib
5)不要阻塞主线程
6)让图片的大小跟UIImageView一样
7)选择正确的集合
8)使用GZIP压缩
初级性能提升:
1.使用ARC进行内存管理
ARC不仅解决了最常见的内存泄漏问题,还有助于程序性能的提升,当程序中的对象不再需要的时候,ARC会在后台自动的帮助销毁对象。值得注意的是ARC并不能避免所有的内存泄漏,使用ARC后可能还会有内存泄漏,主要原因是循环引用。常见的循环引用有:block、定时器、代理等。
2.在适当的情况下使用reuseIdentifier
在没有正确的为UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置reuseIdentifier。为了获得最佳性能,当在tableView:cellForRowATIndexPath:方法中返回cell时,tableView的数据源一般会重用UITableViewCell对象。tableView维护着UITableViewCell对象的一个队列或者列表,这些数据源已经被标记重用了。假如没有使用重用,tableView每次显示一个row时都会配置一个全新的cell,这其实是一个非常消耗资源的操作,并且会影响程序中tableView滚动的效率。
3.尽可能将View设置成不透明
Opaque属性提示绘制系统如何处理view。如果opaque设置为yes,绘图系统会将view看为完全不透明,这样绘图系统就可以优化一些绘制操作以提升性能。如果设置为no,那么绘图系统结合其它内容来处理view。默认为yes。如果屏幕时静止的,那么这个opaque属性的设置与否不是一个大问题。但是,如果view时嵌入到scrollView中,或者是复杂动画的一部分,不设置这个属性的话肯定会影响程序的性能。可以通过模拟器的Debug\Color Blended Layers选项来查看哪些view没有设置为不透明。为了程序的性能,最好将view设置为不透明。
4.避免臃肿的xib
如果必须要使用Xib的话,尽量让xib文件简单。并且每个viewcontroller对应一个xib文件,如果可以的话,把一个viewcontroller的view不同的层次单独的分到一个xib文件中。这是因为当把一个xib文件加载到内存时,xib文件中的所有内容都将被加载到内存中,包括图片。如果一个view不会立即使用,就会造成内存的浪费。而这在storyboard中时不会发生的,因为storyboard还在需要的时候才实例化一个viewcontroller。当加载一个nib文件时,也会将nib文件涉及到的图片或者声音资源加载到内存中,并一直缓存着。
5.不要阻塞主线程
永远都不要在主线程上做繁重的任务。因为UIKit的任务都在主线程中进行,例如绘制、触摸管理和输入响应等。在主线程上做所有任务的风险时:如果你的代码阻塞了主线程,那么程序将出现反应迟钝。在执行I/O操作中,大多数情况下都会阻塞主线程,这些操作需要读写外部资源,例如磁盘或者网络。如果你需要做一些其它类型开销很大的操作(例如执行一个时间密集型的计算或者对磁盘进行读写),那就使用GCD或者NSOperations和NSOperationQueues。
6.让图片的大小跟UIImageView一样
如果需要将bundle中的图片显示到UIImageView中,请确保图片和UIImageView的大小时一样的。因为图片的缩放非常耗费资源,特别是将UIImageView嵌入到UIScrollView中。如果是从远程服务中下载图片,有时候你控制不了图片的吃醋,或者在下载之前无法在服务器上进行图片的缩放,这种情况,当图片下载完成之后,你可以手动进行图片的缩放(最好放在后台线程中),然后再在UIImageView中使用缩放过的图片。
7.选择正确的集合
学习使用最适合的类或者对象是编写高效代码的基础。特别时在处理集合数据时,尤为重要。常见集合类型:
数组:是一个值按顺序排列的一个列表。根据索引可以快速查找,不过根据值进行查找就比较慢,另外插入和删除也比较慢。
字典:存储键值对,根据键可以快速查找。
Sets:是一个值无序排列的列表,根据值可以快速查找,另外插入和删除也比较快。
8.使用GZIP压缩
越来越多的程序依赖于外部数据,这些数据一般来自远程服务器或者其它的外部API。有时候你需要下载这些数据,这些数据可能时XML,JSON,HTML等,问题是在移动设备上的网络时不确定的。用户的设备可能在3G或者WIFI,但不管什么情况下都不要让用户等待。有一个可以优化的选择:使用GZIP对网络传输中的数据进行压缩,这样可以减小文件的大小,并加快下载的速度,对于文本数据特别有用,因为文本具有很高的压缩比。iOS中,如果使用NSURLConnection,那么默认情况下已经支持GZIP压缩了,并且基于NSURLConnection的框架也支持GZIP压缩,如AFNetworking。
二、中级
在性能优化时,当你碰到一些复杂的问题,应该注意使用如下技巧:
9)重用和延迟加载view
10)缓存
11)考虑绘制
12)处理内存警告
13)重用花销很大的对象
14)使用Sprite Sheets
15)避免重新处理数据
16)选择正确的数据格式
17)设置适当的背景图片
18)降低web内容的影响
19)设置阴影路径
20)优化TableView
21)选择正确的数据存储方式
中级性能提升
现在,在进行代码优化时,你已经能够完成一些初级性能优化了。但是下面还有另外一些优化方案,虽然可能不太明显(取决于程序的架构和相关代码),但是,如果能够正确的利用好这些方案,那么它们对性能的优化将非常明显!
9)重用和延迟加载view
程序界面中包含更多的view,意味着界面在显示的时候,需要进行更多的绘制任务,也就意味着需要消耗更多的CPU和内存资源。特别是在一个UIScrollView里加入了许多view。这种情况的管理技巧可以参考UITableView和UICollectionView的行为:不要一次性创建所有的subview,而是在需要的时候创建view,并且当view使用完毕时候将他们添加到重用队列中。这样就可以尽在UIScrollView滚动的时候才配置view,依次避免分配创建view带来的成本,这可能时非常耗费资源的。现在有这样一个问题:在程序中需要显示的view在什么时机创建(比如说,当用户点击某个按钮,需要显示某个view),这里有两种可选方法:一种是在屏幕第一次加载以及隐藏的时候,创建view,然后再需要的时候,再把view显示出来。另一种时知道需要显示view的时候,才创建并显示view。每种方法都有各自的优缺点。使用第一种方法,需要消耗更多的内容,因为创建出来的view一直占据着内存,直到view被release,不过,使用这种方法,当用户点击按钮时,程序会很快的显示出view,因为只需要修改一下view的可见性即可。而使用第二种方法则产生相反的效果,当需要的时候才创建view,这会消耗更少的内存,不过,当用户点击按钮的时候,不会立即显示出view。
10)缓存
在开发程序时,一个重要的规则就是“缓存重要的内容”,这些内容一般不会改变,并且访问的频率比较高。可以缓存些什么内容呢?比如远程服务器的响应内容、突破甚至是计算结果,比如cell的行高。NSURLConnection根据HTTP头的处理过程,已经把一些资源缓存在磁盘和内存中了。你甚至可以手动创建一个NSURLRequest,让其只加载缓存的值。你可以使用NSURLConnection抓取一个URL请求,但是同样可以使用AFNetworking来抓取,这种方法不用修改所有网络相关的代码,这是一个技巧。如果你需要缓存的内容没设计到HTTP请求,那么使用NSCache。NSCache的外观和行为与NSDictionary类似,但是,当系统需要回收内存时,NSCache会自动清理里面的内容。
11)考虑绘制
在iOS中制作漂亮的按钮有多种方法,可以使用全尺寸图片,可缩放图片,或者使用CALayer,CoreGraphics,甚至时OpenGL来手动测量和绘制按钮。当然,这些方法的复杂程度也不同,并且性能也有所区别。简单来说,使用预渲染图片技术是最快的。因为iOS中不用等到在屏幕上显示的时候才创建图形和对形状进行绘制(图片已经创建好了),这样带来的问题时需要把所有的图片都放到程序bundle中,从未增加了程序的大小。因此使用可伸缩图片在这里将派上用场了。可以移除浪费空间的图片(iOS可以重复利用)。并且针对不同的元素(例如按钮)不需要创建不同的图片。不过,使用图片的话会失去代码对图片的控制能力,进而针对不同的程序,就需要重复的生成每一个需要的图片,并反复的放到每个程序中。这个处理过程一般比较慢,另外一点就是如果你需要一个动画,或者许多图片都需要进行轻微的调整(比如多种颜色的覆盖),那么需要在程序中加入许多图片,进而增加了程序bundle的大小。总的来说吗你需要考虑一下什么才是最重要的,绘制性能还是程序大小。一般来说都重要,所以在同一个工程中,应该两种都考虑。
12)处理内存警告
当系统内存偏低时,iOS会通知所有在运行的程序。如果程序收到了低内存警告,在程序中必须尽量释放内存。最佳方法就是移除强饮用的涉及到的缓存、图片对象,以及其它可以在之后使用时还可以重新创建的数据对象。UIKit中提供了如下几种方法来接收低内存警告:实现在appdelegate中的applicationDidReceiveMemoryWarning:方法。在UIViewController子类中重写didReceiveMemoryWarning方法。在通知中心里面注册UIApplicationDidReceiveMemoryWarningNotification通知在收到以上任意警告时,需要立即释放任何不需要的内存。例如,UIViewController默认情况是清除掉当前不可见的view;在UIViewController的子类中可以清除一些额外的数据。程序中没有显示在当前屏幕中的图片也可以release掉。当收到低内存警告时,尽量释放内存是非常重要的。否则,运行中的程序有可能会被系统杀掉。不过,在清楚内存时要注意一点,确保被清除的对象之后还可以被创建出来。另外,在开发程序的时候,用模拟器中的模拟内存警告功能对程序进行测试。
13)重用花销很大的对象
有些对象的初始化非常慢,比如NSDataFormatter和NSCalendar。不过有时候可以避免使用这些对象,例如在解析JSON/XML中的日期时。当使用这些对象时,为了避免性能上的瓶颈,可以尝试尽量重用这些对象,在类中添加一个属性或者创建一个静态变量。注意:如果使用静态变量对象会在程序运行的时候一直存在,就像单例一样。另外,在设置NSDataFormatter的日期格式时,同样跟创建新的一个NSDataFormatter实例对象一样慢。因此在程序中如果需要频繁的处理日期格式,那么对NSDataFormatter进行重用时非常有必要的。
14)使用Sprite Sheets(游戏开发时用的比较多)
15)避免重新处理数据
许多程序都需要从远程服务器中获得数据,以满足程序的需求。这些数据一般是JSON/XML。在请求和接收数据时,使用相同的数据结构非常重要。因为在内存中把数据转换成适合程序的数据格式时需要付出额外代价的。例如你需要在tableView中显示一些数据,那么请求和接收的数据格式最好是数组格式,这样可以避免一些中间操作。
16)选择正确的数据格式
将数据从程序传到服务器有多种方法,其中使用的数据格式基本都是JSON/XML。JSON的解析速度非常快,并且要比XML小得多,也就意味着只需要传输更少数据。并且在iOS5之后,已经有内置的JSON反序列化API了,所以使用JSON是很容易的。不过XML也有它自己的优势:如果使用SAX方法来解析XNL,那么可以边读XML边解析,并不用等到全部的XML获取到了才开始解析,这是与JSON不同的。当处理大量数据时,这种方法可以提升性能并能减少内存的消耗。
17)设置适当的背景图片
在iOS中,跟别的很多东西类似,这里也有两种方法给view设置一个背景图片:一种时可以使用UIColor的colorWithPatternImage方法来创建一个颜色,并将这个颜色设置为view的背景颜色。另一种时可以给view添加一个UIImageView的子视图。如果你有一个全尺寸的背景图片,那么应该使用UIImageView,因为UIColor的那个方法是用来创建小图片的,该图片会被重复使用。此时使用UIImageView会节省很多内存。不过如果你计划用小图片当做背景,那么应该使用UIColor的那个方法。这种情况下绘制速度会很快,并且不会消耗大量的内存。
18)降低web内容的影响
UIWebView非常有用,用它可以很容易显示web内容,甚至可以构建UIKit空间难以显示的内容。但用UIWebView组件没有苹果的safari快。这是因为JIT编译限制了WebView的Nitro引擎的使用。因此为了提高性能,需要调整HTML的大小。首先尽量摆脱JavaScript并避免使用大的框架。另外尽量异步加载JavaScript文件。最后,让使用到的图片跟实际需要的一样大小。如之前提到的,尽量使用sprite sheets,以此节省内存和提升速度。
19)设置阴影路径
如果需要在view或者layer中添加一个阴影,应该怎么做呢?大多数开发者首先将QuartzCore框架添加到工程,然后添加如下代码:(#import // Somewhere later ...UIView *view = [[UIView alloc] init]; // Setup the shadow ...view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);view.layer.shadowRadius = 5.0f;view.layer.shadowOpacity = 0.6;)看起来非常容易,然而CoreAnimation在渲染阴影想过之前,必须通过一个离屏才能确定view的形状,而这个离屏操作非常耗费资源。下面还有一种方法更容易让系统进行阴影渲染:设置阴影路径:(view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];)通过设置阴影路径,iOS就不用总是再计算该如何绘制阴影了。只需要使用你预先计算好的路径即可。有一点不好的是,根据view的格式,自己可能很难计算出路径。另外一个问题就是当view的frame改变时,必须每次都更新一下阴影路径。
20)优化TableView
tableView需要快速的滚动,如果不能,用户就会感觉到卡顿。为了让tableView平滑的滚动,确保遵循了如下建议:
1.设置正确的reuseIdentifier以重用cell
2.尽量将view设置为不透明,包括cell本身
3.避免渐变、图像缩放以及离屏绘制
4.如果row的高度不同,那么将其缓存下来
5.如果cell显示的内容来自网络,那么确保这些内容是通过异步来获取的
6.使用shadowPath来设置阴影
7.减少subview的数量
8.在cellforrowATindexpath中尽量做更少的操作。如果需要做一些处理,那么最好做过一次之后将其缓存起来
9.使用适当的数据结构来保存需要的信息
10.使用rowHeight、sectionFooterHeight和sectionHeaderHeight来设置一个恒定高度,而不要从delegate中获取
21)选择正确的数据存储方式
当需要存储和读取大量的数据时,有如下选择:
1.偏好设置(NSUserDefaults)
NSUserDefault只针对存储小量数据(比如你的级别、声音的开关等)
2.属性列表(XML、JSON或者plist文件)
大量数据保存为结构化的文件也可能会带来问题。一般在解析这些结构数据之前,需要将内容全部加载到内存中,这是很消耗资源的。虽然可以使用SAX来处理XML文件,但是比较复杂,另外加载到内存中的所有对象还都不一定用得上。
3.归档(NSCoding)
由于同样是对文件进行读写,因此依然存在以上问题,所以若要保存大量数据,最好使用SQLite或CoreData。通过他们可以进行具体的查询,只需要获取或者加载需要的对象,避免对数据进行不合理的搜索。在性能方面都差不多。最大区别实际上就在用法上。CoreData代表一个对象模型,而Sqlite只是一个DBMS,一般苹果建议使用coredata。
4.本地数据库(SQLite)
5.CoreData
三、高级
当且仅当下面这些技巧能够解决问题的时候,才使用它们:
22)加速启动时间
23)使用Autorelease Pool
24)缓存图片——或者不缓存
25)尽量避免Data格式化
26)高级性能提升
高级性能提升
寻找一些高明的方法,让自己变为一个全代码大牛?下面这些高级的性能优化技巧可以在适当的时候让程序尽可能的高效运行。
22)加速启动时间
能快速的启动程序非常重要,特别是在用户第一次启动程序时。让程序尽量快速启动的方法就是尽量以异步方式执行任务,例如网络请求,数据访问或解析。另外,避免使用臃肿的xib,因为xib的加载是在主线程中进行的。Storyboard没有这样的问题。注意:在利用xcode进行调试时,watchdog不会运行,所有设备中测试程序启动性能时,不要将设备连接到xcode。
23)使用Autorelease Pool
NSAutoreleasePool负责释放一个代码块中的自动释放现象。一般是由UIKit创建的。
24)缓存图片——或者不缓存
iOS中从程序bundle中加载UIImage一般有两种方法。第一种比较常见:imageNamed:第二种比较少用:imageWithContentsOfFile:。前者的有点在于可以缓存已经加载的图片,这个方法会在系统缓存中根据指定的名字寻找图片,如果找到了就返回,如果没有找到就从指定的文件中加载,并缓存起来,然后再将结果返回。后者只是简单的加载图片,并不会将图片缓存。那么,如果要加载一个很大的图片,并且只使用一次,那么就不需要缓存这张图片,直接用后者就好。如果系统需要重用那么就选择前者。
25)尽量避免Data格式化
如果有许多日期需要使用NSDateFormatter,那么需要小心对待,如前文提到的,任何时候都应该尽量重用NSDateFormatter。然而,如果你需要更快的速度,那么应该使用C语言的方法来解析日期,而不是NSDateFormatter。注意,许多网络api返回的时间戳都是毫秒。