常用技术收集v2panda的技术专题买不来的iOS实用技巧

25条关于提高app性能的技巧

2017-02-07  本文已影响1919人  HuyaRC

分享是每个优秀的程序员所必备的品质

原文出处:https://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks

一、基础 - 这些技巧你要总是想着实现在你开发的App中。

二、中级 - 这些技巧是当你遇到更复杂的情况的时候使用。

三、高级 - 这些技巧你应该只在你很积极认为它们能解决这个问题,而且你觉得用它们很舒适的时候使用。


1.用ARC去管理内存

UIView *view =[[UIView alloc] init];//...[self.view addSubview:view];[view release];

这里极其容易忘记在代码结束的地方调用release,ARC将会自动的,底层的为你做这些工作。 除了帮助你你避免内存泄漏,ARC还能保证对象不再使用时立马被回收来提高你的性能。你应该在你的工程里多用ARC。
值得注意的是ARC不能消除所有的内存泄漏。你依然有可能内存泄漏,这主要可能是由于blocks(块),引用循环,CoreFoundation对象管理不善(通常是C结构体,或者是确实很糟糕的代码)。

2.适当的地方使用reuseIdentifier

NSString *CellIdentifier = @"Cell";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

3.可能的时候设置视图为不透明

这个属性提供了一个提示给图系统如何对待这个视图。如果设置为YES,绘制系统将会把这个视图视为完全不透明。这样允许系统优化一些绘制操作和提高性能。如果设置为NO,绘图系统会复合这个视图和其他的内容,这个属性的默认值是YES

在相对静态的屏幕上,设置opaque属性不会有什么大问题。尽管如此,如果你的视图是嵌入在一个scrollView,或者是一个复杂的动画的一部分,不设置这个属性绝对会影响你的程序的性能。
你也可以使用Debug\Color olor Blended Layers选项 在你的模拟器中形象化的看见没有设置为不透明(opaque)的视图.你的目标应该是尽可能多的设置视图为透明。

4.避免臃肿的XIB文件

Apple’s documentation 如是说:

当你载入一个包含了图和声音资源引用的nib文件时,nib加载代码读取实际的图片文件和音频文件到内存中并缓存它。在OS X中,图片和音频资源被存储在已命名的缓存 中这样你可以在之后需要的时候访问它们。在iOS中,只有图片资源被缓存,访问图片,你使用NSImage或者UIImage的imageNamed:方法来访问,具体使用取决于你的平台。

显然这也发生在使用故事板的时候。尽管如此,我还不能找到这种说法的证据。如果你知道,请给我留言。

5.不要阻塞主进程

阻塞主线程最多的情况就是发生在你的app进行I/O操作,包括牵扯到任何需要读写外部资源的任务,比如读取磁盘或者网络。
你可以异步的执行网络任务使用NSURLConnection中的这个方法:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用第三方框架比如 AFNetworking.
如果你在做任何大开销的操作(比如执行一个耗时的计算,或者读写磁盘)使用Grand Central Dispatch(GCD)或者 NSOperations 和 NSOperationQueues.
使用GCD的模板如下代码所示:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // switch to a background thread and perform your expensive operation dispatch_async(dispatch_get_main_queue(), ^{ // switch back to the main thread to update your UI });});

这里为什么dispatch_async 嵌套在第一个的里面?这是因为任何UIKit相关的代码都必须在主线程上执行。

6. 调整图像视图中的图像尺寸

如果你用UIImageView呈现app束中的图片时,确认图片和UIImageView的尺寸相同。缩放图片会非常的耗时,特别是当你的UIImageView被嵌入UIScrollView。
如果图片是从远程服务器上下载的,有时你没法控制图片尺寸,或者你不能在服务器上在下载之前缩放它。在这些情况下你可以在图片下载完成后手动缩放一次,最好是在后台进程中。然在UIImageView中使用调整尺寸之后的图片。

7. 选择正确集合

学着怎么在手头工作中使用最合适的类或对象是写出高效代码的基本。当时用集合是(collections),这个说法特别对。
可喜的是在苹果开发者文档( Collections Programming Topics)中有详细解释可用类之间的关系,还有解释各个类的适用情况。这个文档是每个使用集合的人的必读文档。
这是一个最常见的集合类型的快速简介:
Arrays:有序的值的列表,用index快速查找,通过值查找慢,insert/delete操作慢。
Dictionaries:存储键/值对.用index快速查找。
Sets: 无序的值列表。通过值快速查找,insert/delete快。

8. 启用Gzip压缩

9. 重用和延迟加载视图

10. 缓存,缓存,缓存

+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image request.HTTPShouldHandleCookies = NO; request.HTTPShouldUsePipelining = YES; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; return request;}

如果想知道更多关于Http caching,NSURLCache,NSURLConnection等内容,请阅读the NSURLCache entry
注意,你可以通过NSURLConnection获取取一个URL请求,AFNetworking也可以。有了这个技巧这样你不用改变任何你的网络代码。
如果要缓存不牵扯到HTTP请求的其他东西,NSCache是很好的选择。
NSCache像NSDictionary,但是当系统需要回收内存的时候会自动的移除内容。

11.考虑绘图

12. 处理内存警告

实现App代理中的applicationDidReceiveMemoryWarning:方法。
重载你自定义UIViewController子类中的didReceiveMemoryWarning方法。

13.重用大开销对象

避免使用这些对象时的性能瓶颈,试着尽可能的重用这些对象。你可以加入你的类中成为一个属性,也可以创建为静态变量。
注意如果你选择了第二种方式,这个对象在app运行的时候会一直保持在内存里,像单例一样。
下面这段代码演示了NSDateFomatter作为一个属性的lazy加载,第一次被调用然后创建它,之后就使用已创建在的实例

// in your .h or inside a class extension@property (nonatomic, strong) NSDateFormatter *formatter; // inside the implementation (.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对象。

14.使用精灵表

15.避免重复处理数据

通过第一次就获取正确格式的数据,在自己的应用程序中你就会避免很多的重复处理工作,使数据符合你的选择的结构。

16.选择正确的数据格式

你可以有很多方法从web 服务中传递数据到你的app中
JSON 是一种通常比XML小且解析更快的格式,它的传输的内容也比较小。自iOS5起,内置的JSON解析很好用 https://www.raywenderlich.com/5492/working-with-json-in-ios-5
尽管如此,XML的一个优势当你使用SAXparsing方法时,你可以传输过程中读取它,在面的非常大的数据时,你不必像JSON一样在数据下载完之后才开始读取。

17.适当的设置背景图片

你可以设置你的视图的背景颜色为UIColor的colorWithPatternImage创建的颜色。

你可以添加一个UIImageView子试图给View,如果你有全尺寸的背景图片,你绝对要用UIImageView,因为UIColor的colorWithPatternImage是重复的创建小的模式图片,在这种情况下用UIImageView方式会节约很多内存。

尽管如此,如果你计划用模式图片背景,你应该是用UIColor的colorWithPatternImage。它更快一些,而且这种情况不会使用很多内存。

self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];

18.减少你的网络占用

UIWebView 是非常游泳的.它非常容易用来显示web内容,甚至创建你app的视窗。这些都是标准UIKit 空间很难做到的。

尽管如此,你可能注意你可以用在你的app中的UIWebView组件并没有Apple的Safari app快。这是Webkit’s的Nitro引擎的限制使用。https://en.wikipedia.org/wiki/Just-in-time_compilation

所以为了获得最佳的性能,你需要调整你的HTML。第一件事是尽可能多的避免Javascript,包括避免大的框架比如jQuery。有时使用vanilla Javascript取代依赖的框架会快很多。

随时随地遵循异步加载Javascript文件的实践。特别当它们不直接影响到页面表现的时候,比如分析脚本。

最后,总是要意识到你在用的图片,保持图片的正确尺寸。正如这个教程前面所提到的,利用精灵表的优势来节约内存和提高速度。

19.设置阴影路径

import <QuartzCore/QuartzCore.h> 
// 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;

看起来非常简单,是吧?
不好的是这个方法有一个问题。核心动画必须要先做一幕动画确定视图具体形状之后才渲染阴影,这是非常费事的操作。
这里有个替代方法让系统更好的渲染,设置阴影路径:

view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

设置阴影路径,iOS不需要总是计算如何绘制阴影。而是用已经计算好的的路径。坏消息是它依赖与你的视图格式,你是视图可能很难计算这个路径。另一个问题是你需要在每次视图的框架改变时更新阴影路径。

20. 优化你的表格视图

表格视图需要快速的滚动,如果不能,用户能确切注意到很滞后。
为了让你的表格视图流畅的滚动,保证你实现了下列的建议。

21. 选择正确的数据存储方式

当要存储和读取大数据的时候你的选择是什么?
你有一些选项,包括:

NSUserDefaults有什么问题呢?虽然说NSUserDefaults是好而且简单,它确实很好只有当你有很少的数据要存(像你的等级,或者音量是开还是关)。一旦你接触大数据,会有更好的其他选择。
保存在结构化文件中也可能有问题。一般的,在解析之前,你需要加载整个文件到内存中,这是非常耗时的操作。你可以使用SAX去处理XML文件,但是那是一个复杂的作法。同时你加载了全部的对象进内存,其中有你想要的也有不想要的。

那么NSCoding怎么样呢?不幸的是,它也同样要读写文件,跟上面说的方法有同样的问题。

你最好的解决方法是使用SQLite或者 Core Data. 通过这些技术,你可以执行特定的查询只加载需要的对象,避免强力搜索方法来检索数据。性能方面,SQLite和Core Data 非常接近。

SQLite 和 Core Data最大的不同就是它们的使用方法。Core Data呈现为一个对象图模型,但是SQLite是一个传统的DBMS(数据库管理系统).通常Apple建议你用Core Data,但是除非你有特殊的原因不让你你会想避开它,使用更低级的SQLite。

如果在你的app中使用SQLite,一个方便的库 FMDB 允许你使用SQLite而不用专研SQLite的C API。

高级性能技巧 寻找一些精英的方式去成为十足的代码忍者?这些高级性能技巧可以合适的时候使用让你的app运行得尽可能的高效。

22. 加速启动时间

App的启动时间非常重要,特别是第一次启动的时候。第一影响意味着太多了!

23. 使用自动释放池

举个例子,如果你创建太多的临时对象在你的代码中,你会注意到你的内存用量会增加直到对象被释放掉。问题是内存只有在UIKit排空(drains)自动释放池的时候才能被释放,这意味着内存被占用的时间超过了需要。
好消息是你可以在你的@autoreleasepool段中创建临时对象来避免上述情况。代码如下所示。

NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) { 
@autoreleasepool { 
NSError *error; 
NSString *fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; 
/* Process the string, creating and autoreleasing more objects. */
}}

在每次迭代之后会自动释放所有的对象。

24.缓存图像

这里有两种方法去加载app束中的Image,第一个常见的方式是用imageNamed. 第二个是使用imageWithContentsOfFile
为什么会有两种方法,它们有效率吗?
imageNamed 在载入时有缓存的优势。文档 documentation for imageNamed是这样解释的:

这个方法看起来在系统缓存一个图像对象并指定名字,如果存在则返回对象,如果匹配图像的对象不在缓存中,这个方法会从指定的文件中加载数据,并缓存它,然后返回结果对象。

作为替代,imageWithContendsOfFile 简单的载入图像并不会缓存。
这两个方法的的演示片段如下:

UIImage *img = [UIImage imageNamed:@"myImage"]; // caching//
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching

如果你加载只使用一次大图片,那就不需要缓存。这种情况imageWithContendsOfFile会非常好,这种方式不会浪费内存来缓存图片。什么时候使用哪一种呢?
然而,imageNamed 对于要重用的图片来说是更好的选择,这种方法节约了经常的从磁盘加载图片的时间。

25. 尽可能避免日期格式化器

如果你要用NSDateFormatter来解析日期数据,你就得小心对待了。之前提到过,尽量的重用NSDateFormatters总是一个好的想法。

然而,如果你需要更快的速度,你可以使用C代替NSDateFormatter来解析日期。 Sam Soffes写了一篇 https://blog.soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments来说明如何用代码来解析 ISO-8601日期串。尽管如此,你可以很容易的修改他的代码例子来适应你的特殊需求。
噢,听起来很棒,但是你相信有更好的办法吗?
如果你能控制你所处理日期的格式,尽可能的选择使用 https://en.wikipedia.org/wiki/Unix_time。Unix时间戳是简单的整数代表从某个起始时间点开始到现在的秒数。这个起始点通常是1970年1月1日 UTC 00:00:00。
你可以容易的把时间戳转换为NSDate,如下面所示:

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

这甚至比C函数更快
注意,很多WEB APIs返回时间戳是毫秒,因为这对于javascript最终来使用和处理数据是非常常见的。只要记住将这个时间戳除以1000再传递给dateFromUnixTimestamp方法即可。

上一篇 下一篇

猜你喜欢

热点阅读