技术干货(9) - 详细性能优化方案

2017-01-11 15:37:24来源:http://www.jianshu.com/p/9d50958aeb2a作者:浮云漫步人点击

第七城市
首先,通过instruments查看哪些方面需要优化

instruments使用介绍.png
优化方式总结
1.尽量把views设置为不透明


如果你有不透明的Views,你应该设置它们的opaque属性为YES。


原因是这会使系统用一个最优的方式渲染这些views。这个简单的属性在IB或者代码里都可以设定。


Apple的文档对于为图片设置不透明属性的描述是:


(opaque)这个属性给渲染系统提供了一个如何处理这个view的提示。如果设为YES, 渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为NO,渲染系统正常地和其它内容组成这个View。默认值是YES。


在相对比较静止的画面中,设置这个属性不会有太大影响。然而当这个view嵌在scroll view里边,或者是一个复杂动画的一部分,不设置这个属性的话会在很大程度上影响app的性能。


你可以在模拟器中用Debug/Color Blended Layers选项来发现哪些view没有被设置为opaque。目标就是,能设为opaque的就全设为opaque!



2.避免过于庞大的XIB


如果你不得不用XIB的话,使他们尽量简单。尝试为每个Controller配置一个单独的XIB,尽可能把一个View Controller的view层次结构分散到单独的XIB中去。


因为当你加载一个XIB的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。Storyboards就是另一码事儿了,storyboard仅在需要时实例化一个view controller.



3.不要阻塞主线程


因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。


一直使用主线程的风险就是如果你的代码真的block了主线程,你的app会失去反应


大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如存储或者网络。



4.在Image Views中调整图片大小


如果要在UIImageView中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同。在运行中缩放图片是很耗费资源的,特别是UIImageView嵌套在UIScrollView中的情况下。


如果图片是从远端服务加载的你不能控制图片大小,比如在下载前调整到合适大小的话,你可以在下载完成后,最好是用background thread,缩放一次,然后在UIImageView中使用缩放后的图片。



5.选择正确的collections(ArraysDictionariesSets)


Apple有一个Collections Programming Topics的文档详尽介绍了可用的classes间的差别和你该在哪些场景中使用它们。这对于任何使用collections的人来说是一个必读的文档。


这是一些常见collection的总结:


Arrays: 有序的一组值。使用index来查找很快,使用value来查找很慢, 插入/删除很慢。


Dictionaries: 存储键值对。 用键来查找比较快。


Sets: 无序的一组值。用值来查找很快,插入/删除很快。



6.懒加载
扩展:Swift的懒加载(lazy)和OC的懒加载是有区别的


想象一下一个用户点击一个按钮的时候需要呈现一个view的场景。有两种实现方法:



创建并隐藏这个view当这个screen加载的时候,当需要时显示它;



当需要时才创建并展示。



用第一种方案的话因为你需要一开始就创建一个view并保持它直到不再使用,这就会更加消耗内存。然而这也会使你的app操作更敏感因为当用户点击按钮的时候它只需要改变一下这个view的可见性。


第二种方案则相反-消耗更少内存,但是会在点击按钮的时候比第一种稍显卡顿。




Cache, Cache, 还是Cache!


缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。


比如:远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。


NSURLConnection默认会缓存资源在内存或者存储中根据它所加载的HTTP Headers。你甚至可以手动创建一个NSURLRequest然后使它只加载缓存的值。


下面是一个可用的代码段,你可以可以用它去为一个基本不会改变的图片创建一个NSURLRequest并缓存它


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

如果你需要缓存其它不是HTTP Request的东西,你可以用NSCache,NSCacheNSDictionary类似,不同的是系统回收内存的时候它会自动删掉它的内容。



8.权衡渲染利弊


简单来说,就是用事先渲染好的图片更快一些,因为如此一来iOS就免去了创建一个图片再画东西上去然后显示在屏幕上的程序。问题是你需要把所有你需要用到的图片放到app的bundle里面,这样就增加了体积 – 这就是使用可变大小的图片更好的地方了: 你可以省去一些不必要的空间,也不需要再为不同的元素(比如按钮)来做不同的图。


然而,使用图片也意味着你失去了使用代码调整图片的机动性,你需要一遍又一遍不断地重做他们,这样就很浪费时间了,而且你如果要做一个动画效果,虽然每幅图只是一些细节的变化你就需要很多的图片造成bundle大小的不断增大。



9.处理内存警告
app delegate中使用applicationDidReceiveMemoryWarning: 的方法
UIViewControllerdidReceiveMemoryWarning
注册并接收 UIApplicationDidReceiveMemoryWarningNotification的通知


10.避免反复处理数据


许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。在内存中操作数据使它们满足你的数据结构是开销很大的。


比如你需要数据来展示一个table view,最好直接从服务器取array结构的数据以避免额外的中间数据结构改变。


类似的,如果需要从特定key中取数据,那么就使用键值对的dictionary。



11.正确设定背景图片


如果你使用全画幅的背景图,你就必须使用UIImageView因为UIColorcolorWithPatternImage是用来创建小的重复的图片作为背景的。这种情形下使用UIImageView可以节约不少的内存:


UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];

如果你用小图平铺来创建背景,你就需要用UIColorcolorWithPatternImage来做了,它会更快地渲染也不会花费很多内存:


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


12.设定Shadow Path

#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;


上面的代码不得不先在后台得出你的图形并加好阴影然后才渲染,这开销是很大的。


使用shadowPath的话就避免了这个问题:


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


使用shadow path的话就不必每次都计算如何渲染,它使用一个预先计算好的路径。但问题是自己计算path的话可能在某些View中比较困难,且每当view的frame变化的时候你都需要去update shadow path.




13.优化Table View


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


14.选择缓存图片的方式




常见的从bundle中加载图片的方式有两种,一个是用imageNamed,二是用imageWithContentsOfFile,第一种比较常见一点。


imageNamed的优点是当加载时会缓存图片。imageNamed的文档中这么说:
这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。相反的,imageWithContentsOfFile仅加载图片。


如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用imageWithContentsOfFile足矣,这样不会浪费内存来缓存它。



15.避免日期格式转换


如果你要用NSDateFormatter来处理很多日期格式,应该小心以待.就像先前提到的,任何时候重用NSDateFormatters都是一个好的实践。


如果你可以控制你所处理的日期格式,尽量选择Unix时间戳。你可以方便地从时间戳转换到NSDate:


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

需要注意的是,许多web API会以微秒的形式返回时间戳,因为这种格式在javascript中更方便使用。记住用dateFromUnixTimestamp之前除以1000就好了。



模拟器常用性能测试工具

Color Blended Layers
这个选项基于渲染程度对屏幕中的混合区域进行绿到红的高亮(也就是多个半透明图层的叠加)
由于重绘的原因,混合对 GPU 性能会有影响,同时也是滑动或者动画帧率下降的罪魁祸首之一
标示混合的图层会为红色,不透明的图层为绿色,通常我们希望绿色的区域越多越好。

Color Misaligned Images
会高亮那些被缩放或者拉伸以及没有正确对齐到像素边界的图片(也是非整形坐标)。被缩放的图片会被标记为黄色,像素不对齐则会标注为紫色。
这些中的大多数通常会导致图片的不正常缩放,如果把一张大图当缩略图显示,或者不正常的模糊图像

Color Copied Images
有时候寄宿图片(layer.content)的生成是由 Core Animation被强制生成的一些图片,然后发送到渲染服务器,而不是简单的指向原始指针
这个选项把图片渲染成蓝色
复制图片对内存和 CPU 使用来说都是一项非常昂贵的操作,所以应该尽可能的避免
这主要是因为该图片的色彩格式不能被GPU直接处理,需要在CPU这边做转换,假如在主线层做这个操作对性能会有一定的影响。

Color Offscreen-Rendered Yellow
这里会把那些需要离屏渲染的图层高亮成蓝色
这些图层很可能需要用 shadowPath 或者 shouldRasterize 来优化


以上性能优化中,有效的检测 混合模式 和 拉伸图像 在开发中能够提升图像的性能


图像复制几乎遇不到


离屏渲染主要用于 cell 的性能优化





第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台