生活成长史iOS开发iOS开发

iOS App性能优化技巧

2016-08-10  本文已影响732人  楚简约

      性能对 iOS 应用的开发尤其重要,如果你的应用失去反应或者很慢,那么App Store的评论显而易见。然而由于iOS设备的限制,有时搞好性能是一件难事。开发过程中你会有很多需要注意的事项,你也很容易在做出选择时忘记考虑性能影响。

      要注意的是,这里列出的其中一些建议是有代价的,所建议的方式会提升app的速度或者使它更加高效,但也可能需要花很多功夫去应用或者使代码变得更加复杂,所以要仔细选择。

1. 用ARC管理内存,静态分析product ------>  Anlyze 或者快捷键 shift+com+B,凡是遇到 retain 、copy 、create 出的对象, 都需要进行 release.例如创建绘制路径CGPathCreateMutable()就需要释放CGPathRelease(path),而属于CoreFoundation另外一种释放方式CFRelease(path).CGXxxxxCreate 对应的就有 CGXxxxxRelease,通过 CFRelease(任何类型);可以释放任何类型。

2. 在正确的地方使用reuseIdentifier重用cells。

3. 尽可能使Views不透明,尽量使所有的view opaque,包括cell自身.

4. 避免庞大的XIB, xib官方解释: xib是XML文件定义和配置的一组对象(Xib是XML格式的文件),并专门操纵主要观点(UIView子类)。Xcode具有友好的编辑器,可以显示这些意见,它是一个运行的应用程序,使得它的配置和设计布局非常容易(节省很多行代码)。xib优势:相比纯代码,大大缩短了UI界面搭建的时间,提高了开发效率,可视化的效果,更直观的设计,在版本管理上和纯代码的差异并不是很大,易读易维护。问题: Xib无法进行逻辑判断,很难在运行时进行配置,Xib在使用时,经常要通过代码的补充,来完成功能实现。多人开发中通过代码修改Xib的属性,可能造成混乱和不可预计的问题。从程序员角度,可读性较差。不利于统一管理和维护。使用SVN等代码管理工具时,Xib会产生无用的记录,以及版本更新的缺陷。从Xcode5开始,苹果对这一方面问题进行了优化,比如在版本管理上,也可以很好的查找修改记录了。xib中的设置往往并非最终设置,UI设计会被代码所覆盖。

5. 不要block操作主线程,在执行 block 之前,首先会寻找合适的线程来执行block,然后阻塞这个线程,直到 block 执行完毕,寻找线程的规则是: 任何提交到主队列的 block 都会在主线程中执行,在不违背此规则的前提下,尽可能的在当前线程执行 block。在主线程中执行的代码,也很可能不是运行在主队列中(反之也是)。

6. 在ImageViews中调整图片大小,例如: imageView.clipsToBounds = YES;  [imageView.layer setCornerRadius:50];

这样设置会触发离屏渲染,比较消耗性能。比如当一个页面上有十几头像这样设置了圆角会明显感觉到卡顿。注意:png图片UIImageView处理圆角是不会产生离屏渲染的。(ios9.0之后不会离屏渲染,ios9.0之前还是会离屏渲染)。

7. 选择正确的Collection,合理使用FlowLayout布局,使用代理方法重用cells。例如: 下图中有多少个sections呢?

答案是2个。即我们在numberOfSectionsInCollectionView:方法中返回2即可。

8. 打开gzip压缩。服务端使用gzip压缩,可以大幅度减小传输包的体积,加快客户端网络请求速度,为用户节省流量。当服务器返回的httpHeader的"Content-Encoding" 属性的值是gzip时,数据会自动被解压缩,但有时候在客户端还没拿到数据的时候,就已经被某些网关解压了,这样gzip就没有起到作用。因此可以约定其他策略,防止网关解压,例如在别的头属性中标记gzip. 如此,就需要我们自己来解压gzip数据。方法如下:添加framework库中的libbz2.1.0.dylib;给NSData添加- (NSData *)gzipUnpack方法解压:并引入头文件  #import "zlib.h"将拿到的data直接调用UnPack方法就完成解压了。如果编译出现link error,就到Target的设置,找到"Other Linker Flags"这一项,添加-lz就可以了。

9. 重用和延迟加载Views。如果我们在启动程序时,就把所有的views新建出来,等到用到时就加载。这样的话,毫不疑问就会在内存存放着许多的views,这样明显不行。系统的view默认都是懒加载过程,只有用到view的时候,才会新建加载,节省CPU的消耗,在我们写程序时也一定参考这种方法。

10. Cache使用。一种是NSUserDefaults,这个是应用级别的cache、持久的不会随程序的关闭、关机而消失,一般存储应用程序的配置信息、默认数据等;不适合存储业务数据;数据量也不易过大。支持的存储数据类型:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。另一种是NSCache苹果提供的一套缓存机制,和NSMutableDictionary(缓存池)使用起来相似, 线程安全,Mutable开发的类一般都是线程不安全的, 当内存不足时会自动释放内存(所以从缓存中取数据的时候总要判断是否为空) ,指定缓存的限额(countLimit),当缓存超出限额自动释放内存。

11. 权衡渲染方法。在iOS中随处可见的不同的漂亮的按钮,一般状态下我们都是使用美工已经切好的图片来设置,同样我们也可以用CALayer、CoreGraphics设置是OpenGL来完成这些功能。这两种方法各有利弊,使用事先渲染过的图片更快一些,省去了创建一个图片接着进行渲染最后显示图片的程序,但是我们需要将这些图片放入app的bundle中等待着被使用,这回加大内存空间的使用。通过渲染的方式可能会复杂一些但是当遇到动画功能时,渲染的优势就显现出来,一般的动画我们可以通过图片帧来完成,但是遇到复杂的图片时,就会显心有余而力不足了。

12. 处理内存警告。在 app delegate中使用applicationDidReceiveMemoryWarning:方法在自定义的控制器UIViewController子类中重写父类的didReceiveMemoryWarning方法注册接受UIApplicationDidReceiveMemoryWarningNotification的通知,一旦接受到通知就尽快释放不用的内存空间, 值得一说的是UIViewController默认就会移除不可见的view,所以它的一些子类可以重写相关的方法,删除一些用不到的数据。

13. 重用大开销的对象,这个就不用多说,例如cells之类。还比如NSDateFormatter和NSCalendar类的初始化非常慢,我们就通过使用属性来延迟加载NSDateFormatter对象(懒加载)。

14. 使用Sprite Sheets, 主要用于游戏开发中,由于游戏肯定需要炫酷的画面,使用Sprite sheets可以让渲染的速度加快。同样对于敌人、炮弹这些动作类元素,你可以重用这些sprites而不用每次都要重新创建。

15. 避免反复处理数据。小量数据处理:NSKeyedArchiver/NSUserDefaults/Write写入方式。大量数据SQLite/CoreData。基本的plist适合Objective-C中内置的数据类,要想存储和读取自定义的对象,需要使用归档(archive)和反归档(unarchiver). 嵌入式数据库SQLite在处理大型数据时优势明显。在不同的场合使用适当的方法,是开发程序时的原则。

16. 选择正确的数据格式。从网络或者app传输数据常用到两种格式:JSON、XML,这两种方式也各有优劣。解析JSON数据对比XML来说会更快一些,JSON也通常用于小数据的传输。对于XML数据来说,使用SAX来解析XML数据就像解析本地文件一样,不必要解析JSON一样等整个文件文档下载完成后才开始解析。这样当你处理大数据时就会极大降低内存消耗和提高性能。当解析数据完成后,加载数据也需要注意,我们最好将数组数据转化为模型数据装入数组中,方便数据的展示。对于需要从特定的key中取数据,那就使用键值对进行操作。

17. 正确地设置BackgroundImages。在如下类进行设置:UIBarButtonItem/UIButton/UINavigationBar/UISearchBar/UISegmentedControl/UITabBar/UIStepper/UIToolbar。

18. 减少使用Web特性。在app中UIWebView很有用,可以用来展示网页内容或者创建UIkit很难做到的动画效果。但是UIWebView的加载并不像我们想象的那么快,这是受web的一些特性的影响。因此想要提高性能就要调整HTML了,尽可能的移除不必要的JavaScript,避免使用过大的框架,使用原生js即可。我们要特别注意,我们要保证要使用的图片符合你使用的大小,可以使用Sprite Sheets提高加载速度和节约内容。

19. 设定ShadowPath。QuartzCore框架的项目,设置阴影效果如下:

[myView.layer setShadowOpacity:0.5]  阴影效果

然而,这种最简单的添加阴影的方法在性能上却不是最佳途径。如果对这个添加阴影的View(如果它是一个UITableViewCell的一部分)做一些动画,您可能会注意到在动画不是很流畅,有卡顿。这是因为计算阴影需要Core Animation做一个离屏渲染,以View准确的形状确定清楚如何呈现其阴影。解决方法:只要你提前告诉CoreAnimation你要渲染的View的形状Shape,就会减少离屏渲染计算  [myView.layer setShadowPath:[[UIBezierPath  bezierPathWithRect:myView.bounds] CGPath];加上这行代码,就减少离屏渲染时间,大大提高了性能。

20. 优化的Table View。正确使用reuseldentifier重用可用的cell, 尽量保证views的opaque属性为YES不透明, 对于图片的调整,在加载前要调整frame适当后再加载, 缓存行高, 如果cell内现实的内容来自网络使用异步加载缓存请求结果, 尽量控制subViews的数量,不宜过多, 尽量不适用 cellForRowAtIndexPath: 方法,如果要用到它,就缓存请求的结果, 使用正确地数据结构来存储数据, 最好使用属性 “rowHight”、“sectionFooterHeight”、“sectionHeaderHeight”来设置高度,最好不适用代理方法来设置,提高代码性能。

21. 选择正确的数据存储选项。iPhone会为每一个应用程序生成一个私有目录,这个目录位于/Users/*your user name*/Library/Application Support/iPhone Simulator/5.0/Applications,并随机生成一个数字+字母的目录名,在每一次应用程序启动时,这个目录名都会随机变化。

22. 加速启动时间(Speed up Launch Time )。减少程序启动过程中的任务, App的启动时间非常重要,特别是第一次启动的时候。第一影响意味着太多了!最大的事情是保证你的App开始尽量的快,尽量的多的执行异步任务,不如网络请求,数据库访问,或者数据解析。

23. 使用Autorelease Pool自动释放池。NSAutoreleasePool负责释放在代码块中的自动释放对象。通常,它是被UIKit自动调用的。但是也有一些场景我们需要手动创建NSAutoreleasePools。举个例子,如果你创建太多的临时对象在你的代码中,你会注意到你的内存用量会增加直到对象被释放掉。问题是内存只有在UIKit排空(drains)自动释放池的时候才能被释放,这意味着内存被占用的时间超过了需要。在每次迭代之后会自动释放所有的对象。你可以阅读更多关于NSAutoreleasePool的内容 Apple’s official documentation。

24. 选择是否缓存图片。这里有两种方法去加载app束中的Image,第一个常见的方式是用imageNamed.,第二个是使用imageWithContentsOfFile。为什么会有两种方法,它们有效率吗?imageNamed 在载入时有缓存的优势,官方文档 documentation for imageNamed是这样解释的:这个方法看起来在系统缓存一个图像对象并指定名字,如果存在则返回对象,如果匹配图像的对象不在缓存中,这个方法会从指定的文件中加载数据,并缓存它,然后返回结果对象。作为替代,imageWithContendsOfFile 简单的载入图像并不会缓存。如果你加载只使用一次大图片,那就不需要缓存。这种情况imageWithContendsOfFile会非常好,这种方式不会浪费内存来缓存图片。然而,imageNamed 对于要重用的图片来说是更好的选择,这种方法节约了经常的从磁盘加载图片的时间。

25. 尽量避免日期格式转换。如果你要用NSDateFormatter来解析日期数据,你就得小心对待了。之前提到过,尽量的重用NSDateFormatters总是一个好的想法。如果你可以控制你所处理的日期格式,尽量选择Unix时间戳。你可以方便地从时间戳转换到NSDate:

- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp { return [NSDate dateWithTimeIntervalSince1970:timestamp]; }。

26.在Xcode7.2中new一个新的UIView,会默认添加如下代码:

drawRect方法

从这段话中在此证明,UIView确实没有drawRect:的默认实现。而空实现会对性能有负面影响,网上的说法是,是进入这个方法之前,需要生成绘制上下文,也就是在这个方法中使用UIGraphicsGetCurrentContext()这个方法获取的上下文。而在子类中提供了drawRect:后,堆栈是这样的

提供了drawRect:后堆栈

显然,drawLayer:inContext会调用drawRect:来绘制,所以在UIView中实现drawLayer:inContext是不可取的,你很容易忘记调用drawRect:,即便你记得,你也并不知道drawLayer:inContext又没做了其他事情,考虑不应该drawRect:的空实现,而上下文也会在drawLayer:inContext:中传入,我猜测可能苹果在默认的drawLayer:inContext:中先判断了delegate是否实现了drawRect:方法,如果是则调用delegate的方法,否则自己管理这个绘制的过程,并对这个过程做了许多优化。总而言之,

在UIView中,永远不要override drawLayer:inContext:这个方法。

最后在iOS上进行性能分析的时候,可以首先考虑借助instruments这个利器分析出问题出在哪,不要凭空想象,不然你可能把精力花在了1%的问题上,最后发现其实啥都没优化,比如要查看程序哪些部分最耗时,可以使用Time Profiler,要查看内存是否泄漏了,可以使用Leaks等。关于instruments网上有很多资料,作为一个合格iOS开发者,熟悉这个工具还是很有必要的。

-----

我是楚简约,感谢您的阅读,

喜欢就点个赞呗,“❤喜欢”,

鼓励又不花钱,你在看,我就继续写~

非简书用户,可以点右上角的三个“...”,然后"在Safari中打开”,就可以点赞咯~

----

文/楚_简书书_简约(简书作者)。

上一篇下一篇

猜你喜欢

热点阅读