ios 性能优化

来源:互联网 发布:java中的各种buffer 编辑:程序博客网 时间:2024/05/24 03:41

核心动画在设计的时候就考虑了性能。

它首先是层级别的呈现,并且设计运行在小型的设备上(iphone和itouch),这些设备内存有限,并且cpu和gpu不如桌面电脑上的强大,核心动画是被设计的比较高效的,但是并不意味着你就可以在代码中随便用。

任何复杂的系统都会考虑性能的问题。幸运的是核心动画在处理复杂动画时,已经帮你处理的很多性能问题,你也需要改善一下代

码让基于核心动画的应用程序具有更好的性能提升。


这一章展示给你的是,如何来充分利用核心动画。

本章的开始会有一些指南。这些都是你应该记住的,当你做核心动画的程序时,你应该通过这些指南精炼你的代码。然后你会了解

到如何平衡GPU,怎么用多线程的动画,并且使用CATiledLayer呈现大的图像给用户,使你的应用程序不至于图像太大而陷入困

境。


硬件加速

核心动画的一个重要的好处就是它利用了mac的硬件的优点。

先前,开发者需要在应用上做动画时,动画产生需要在CPU上,并且会耗掉cpu的循环,这样就会使程序慢。制作用户界面动画会消

耗大量的时间,通常还要涉及到OpenGL或其他的一些技术与图形处理单元(GPU)的工作。使用核心动画,你会能够自由的使用

硬件加速。你仅仅需要做的就是定义一个动画,开启它,让它运行。你不必担心装载到GPU中,因为核心动画都帮你自动处理了。


当您在设计用户界面时,要牢记。那些你认为是一个复杂的动画,但是放到GPU上执行可能微不足道。 GPU是专门来做屏幕上矩形

的移动,三维空间转化等。只有当GPU的限制产生时,我们开始看到性能下降。


最小原则

下面的段落所呈现的原则,都是你需要在做核心动画时,需要牢记的规则。

避免幕后渲染

不管你是在桌面应用程序还是在touch设备上,你需要限制你的绘制,仅仅当这些区域对于用户可见时再绘制。幸运的是,-

drawRect:方法只有当矩形区域为脏时,才会传递过来,因此你可以精确的控制每个循环的绘画次数。


限制滤镜和阴影

滤镜和阴影需要耗费大量的时间来计算和渲染在核心动画上。因而,建议尽量保持小并且有可能的话,避免上述的情况。

这有一些技巧:

尤其是滤镜的情况,它可能要渲染一个静态的,临时的图像。在动画假设被渲染时,这个静态的图像可能被使用来代替需要动画渲

染的层。当动画完成时,渲染的层就被交换到了后面。虽然这不能被应用到所有的情况,这也会产生性能的优化。这些动作可以通

过在最上层调用-drawInContext来创建这个用来交换的静态图片。


阴影也是代价很高的。

因为他们属性部分透明的层,它需要大量的计算,来决定每个像素,因为每个像素都需要计算,直到有不透明的层遇到。

如果阴影重叠的话,就增加了消耗。考虑限制只有最外层的阴影,并允许内层不产生任何阴影。参见“最小alpha混合”在本章的后

面。


明智的使用变换

转换提供了一个很好的方式,给用户一个视觉感受,应用程序怎么改变并且变成了什么样。从窗口滑出或者滑入的方式,给用户了

一个明显的视觉感触。然而例如要转换滤镜和阴影话,这是对性能的一个很大考验。


因而,在设计应用程序时,你应该考虑在可视层中有多少转换或者它们应该转换多快。

避免嵌套转换

作为核心动画的强大之处,可以彼此同时转换多个层。例如,你可以在一些层中转换层中所有z轴。然而,要注意到这些转换都是实

时执行的,没有缓存。因而,当你移动或者让层做动画时,这个层有了很多层的转换,每个转换都需要动画重新计算它们的帧。


为了增加性能,避免使用多层级的转换,当动画运行时。

限制透明度的混合

前面讨论了嵌套转换,透明度混合也是实时计算的。当一个层是部分透明时,透明部分的动画就要在动画帧中重新计算。记住这

些,当动画层中有透明时,尽量减少计算。滑动层之后的层是透明的会导致极大的性能消耗。


幸运的是,有个方法可以找到那个层有透明度混合,从而移除它。对于iphone这种有限的资源它是非常有用的。来核对透明度混

合,启动iphone下的应用程序,使用instruments。在里面增加核心动画instrument。


当你的应用程序在iphone上运行时并且你也附带instrument到程序上,那么转换到核心动画的instruments上。

只要运行,你立马就可以看到效果。整个iphone程序被分成红色和绿色。事实上,这里没用应用程序运行,你可以看到iphone主屏

幕的效果。

当可用时,不同的颜色混合就会展示不同的颜色,因此你能快速的定位到应用程序那些有问题。绿色罩住的区域是没有颜色混合

的,而红色的是有的。目标就是来减少红色区域。例如,在TransparentCocoaTouch应用程序中,你可以看到所有的UILabel对象都

有一个透明背景。

当你检查你的代码时,你就会看到那些区域是有问题的。这里看-initWithFrame:reuseIdentifier:中的CustomTableViewCell对象,问

题就在这里。

-(id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString*)ident {  if (!(self = [superinitWithFrame:frame reuseIdentifier:ident])) return nil;  UIView *contentView = [self contentView];  imageView = [[UIImageViewalloc] initWithFrame:CGRectMake(5, 5, 64, 64)];  [imageView setContentMode:UIViewContentModeScaleAspectFit];  [contentView addSubview:imageView];  [imageView release];  titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];  [titleLabel setFont:[UIFontboldSystemFontOfSize:14.0f]];  [titleLabel setBackgroundColor:[UIColor clearColor]];  [contentView addSubview:titleLabel];  [titleLabel release];  descriptionLabel = [[UILabelalloc] initWithFrame:CGRectZero];  [descriptionLabel setNumberOfLines:0];  [descriptionLabel setFont:[UIFont systemFontOfSize:6.0f]];  [descriptionLabel setBackgroundColor:[UIColor clearColor]];  [contentView addSubview:descriptionLabel];  [descriptionLabel release];  [[NSNotificationCenterdefaultCenter] addObserver:self selector:@selector(imageUpdated:)      name:kImageDownloadComplete  object:nil];  return self; }  

就像你看到的,UILabel对象,titleLabel和descriptionLabel,两个的背景颜色都是被设定为[UIColor clearColor],引起了颜色混合的

发生。为了纠正这些,改变UIColor和整个UITableViewCell的背景颜色一样。这就减少了透明度的混合,提高了性能。

- (id)initWithFrame:(CGRect)framereuseIdentifier:(NSString*)ident {  if (!(self = [superinitWithFrame:frame reuseIdentifier:ident])) return nil;  UIView *contentView = [self contentView]; imageView = [[UIImageViewalloc] initWithFrame:CGRectMake(5, 5, 64, 64)];  [imageView setContentMode:UIViewContentModeScaleAspectFit];  [contentView addSubview:imageView];  [imageView release];  titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];  [titleLabel setFont:[UIFontboldSystemFontOfSize:14.0f]];  [titleLabel setBackgroundColor:[UIColor whiteColor]];  [contentViewa ddSubview:titleLabel];  [titleLabel release];  descriptionLabel = [[UILabelalloc] initWithFrame:CGRectZero];  [descriptionLabel setNumberOfLines:0];  [descriptionLabel setFont:[UIFont systemFontOfSize:6.0f]];  [descriptionLabel setBackgroundColor:[UIColor whiteColor]];  [contentView addSubview:descriptionLabel];  [descriptionLabel release];  [[NSNotificationCenterdefaultCenter] addObserver:self selector:@selector(imageUpdated:)         name:kImageDownloadComplete      object:nil];  return self; }  

平铺层

核心动画有一个强大的功能,使您可以快速查看应用程序的详细图像。CATileLayer是被设计成来展示大图像,但是不用把整个图像

导入到内存中,因而引起了性能的提升。


为了演示CATiledLayer这个有用的特征,打开TiledLayers示例应用程序。在这个应用程序中一个很大的图像(6064*4128)是被导入并

且还具有放大缩小的能力。正常情况下,一个很大的图像是被导入到内存中,这样会引起性能问题。然而,通过导入图像到

CATiledLayer中,核心动画来控制所有的内存问题。核心动画会装载图像需要展示的一部分而非整个图像。你需要配置的就是在

CATileLayer上给它一些细节的东西,就是根据尺寸来指定缩放的等级。


在TiledLayer应用程序中,一个简单的CATileLayer是在主窗口中初始化并且分配了一个NSSegmentedControl来管理缩放等级。


当界面是被设计时,构造CATiledLayer在-awakfromNib方法中。
- (void)awakeFromNib {  NSString *imagePath = [[NSBundle mainBundle] pathForResource:@”BigSpaceImage” ofType:@”png”];  NSData *data = [NSDatadataWithContentsOfFile:imagePath];  image = [[CIImageimageWithData:data] retain];  tiledLayer = [CATiledLayer layer];  [tiledLayer setBounds:CGRectMake(0, 0, 6064, 4128)];  float midX =NSMidX(NSRectFromCGRect([[view layer] frame]));  float midY =NSMidY(NSRectFromCGRect([[view layer] frame]));  [tiledLayer setPosition:CGPointMake(midX, midY)];  [tiledLayer setLevelsOfDetail:4]; // number of levels [tiledLayersetTileSize:CGSizeMake(256, 256)];  [tiledLayer setDelegate:self];  [[view layer] addSublayer:tiledLayer]; }  

当应用程序获得图像的路径时,那个路径通过CIImage的调用被导入然后存储起来用。CATileLayer是被初始化然后初始化将要展示

的图像边框。下面就定位图像的位置并且配置可用缩放的等级。最后一步就是吧AppDelegate作为代理分配给该层,以便于接收绘图

事件。


NSSegmentedControl是绑定在-zoom:方法上,每当segmented control改变状态时就发送事件。选择段的位置是被使用来决定图像

目前的缩放比例,在CATileLayer上展示的图像。这些可以通过它的sublayerTransform的CATransform3DMakeScale调用来实现,在

段控制器上传递一个x和y的值。

- (IBAction)zoom:(id)sender {  CGFloat zoom = 1.0 / ([senderselectedSegment] + 1);  [[view layer] setSublayerTransform:CATransform3DMakeScale(zoom, zoom, 1.0)];  }  

最后你要实现的方法就是CATiledLayer的一个代理。这个方法可以使我们重载-drawInContext:方法,而不用继承CALayer。在默认

的-drawInContext:方法实现中,它会寻找一个代理,核对代理是否实现了-drawLayer:inContext:方法。如果这个情况为真,那么-

drawLayer:inContext就会被调用。

- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {  [[CIContextc ontextWithCGContext:context options:nil] drawImage:imageatPoint:CGPointMake(0, 0) fromRect:CGRectMake(0, 0, 6064, 4128)];  }  


这个方法使用了在-awakFromNib中初始化的图像,并且使用了图像尺寸的边框绘制了它到了图像上下文中。应该注意的是,你不需

要控制任何的缩放计算,变换等方法。CATileLayer都自动帮你控制了。



这些如何工作

CATiledLayer导入图像成快,名字就是由此而来。每个块都响应一个图像的细节,根据你设定的块的尺寸。当你要求某个图像的细

节CATileLayer会根据原始的图像,缩放到渴望的尺寸,并且在屏幕上渲染之前,分割它成不同的块。因为这些块都被CATiledlayer

缓存下来,所以你可以滚动图像,并且很快的改变图像的细节。


CATiledLayer会缓存足够多的块,而当它也会根据需要扔掉不同的块。如果将来你再次需要他就会再次的生成。这意味着块的呈现

是一个异步的过程,那么用户就会看到延时,之后才会绘制到层上。一个很好的例子就是google地图的应用程序,如图12-5.如果你

缩放块的等级,图像不会立刻展示,而是有一些网格组成。当块装载完成时,就会代替网格显示出来。


多线程的动画

核心动画是线程安全的,因为它是安装多核系统的思想设计的。


这意味着你在多线程中操控核心动画,不会影响到其他的内部数据,这点非常的重要。然而,核心动画有一个地方不是线程安全

的,就是当它进入到CALayer的属性中时。


如果你的应用程序在主线程进入到属性中时,又在其他线程中进入到该属性,结果是不确定的。它可能的结果是,要么动画没有效

果,要么程序crash。当你要尝试获得一个属性,并且改变它时,你需要先存储属性先前的状态,并且这个行为要加锁,使这个过程

具有原子性。

[layer lock]  float opacity = layer.opacity;layer.opacity = opacity + .1;  [layer unlock];  


在这个例子中,[CATransactionlock]用来防止任何其他线程获得锁,会有效的阻碍其他线程。这种序列话的进入到属性中,保证在我

们处理的过程中,属性不会被改变。


当需要层工作在多线程中,我们需要注意的如下:

获取一个锁时间太长的话,会让界面停止绘制。因为进入到层是被锁住了,不能在其他线程中进入,系统的其他部分也不能进入直

到这个锁被释放了。记住任何时候你锁住了一个层,就要尽快的解锁。


第二个问题是所有线程都遇到的问题,不仅仅是核心动画,那就是死锁。如果你锁住了一个层在一个线程中,这个层需要进入另一

个属性,但是这个属性是被另一个线程锁住,那么整个应用程序就会无响应,结果就是程序的crash。


滤镜的多线程

不像核心动画,核心图像滤镜不是线程安全的。这以为这你应该仅仅在单线程中处理核心图像。然而,也可以通过键值对(KVC)和关

键路径在多线程中控制滤镜。例如,如果你有一个层滤镜名字是acme,并且有一个属性叫做job,你可以通过下面的代码进行调整

[layer setvalue:myValue forKeyPath:@”layer.filters.acme.job”];

这将保证改变是原子的,因而是线程安全的。

线程和运行循环

当核心动画在多线程中工作时,你需要意识到的是在任何线程上调用-setNeedsDisplay,都会给层一个-display的消息。如果标记一

个层需要显示,而非在主线程中,那么你就必须等待一个运行循环去确保线程已经运行了-display的方法。如果不这样,-display就从

来不被调用,因为线程已经终结了,会引起一些异常的效果。


尽管它是可能在线程中调用-setNeedsDisplay,而不在主线程中。但是这对绘制工作影响很小的,但多数情况下建议使用-

performSelectorOnMainThread:方法,让它在主线程中运行。


总结

这一章,我们看到了关于性能方面的一些建议和策略。但是在处理性能之前,建议先完成代码。不要试着花费大量的时间来优化代

码,除非性能的问题已经发生。


这里也建议通过一些记录结果来测试一些潜在的瓶颈。有时候,一个简答的fps(每秒的帧率)记录就可以测试出来瓶颈。利用这些记

录,我们就能确定那些性能改变是正确的,代替盲目的找到一些无关紧要的因素。



0 0
原创粉丝点击