大型自定义UIView - CABackingStoreUpdate性能

Ell*_*rry 12 iphone objective-c uiview ios4 ios

为什么在iOS 4.3.5上,"大"(960 x 1380)自定义UIView执行CABackingStoreUpdate效率低下,如何提高绘图操作的性能?

不完全确定我的意思?继续阅读......

注意:

随着我对这个问题的理解的发展,这个问题也是如此.因此,问题本身是相似的,但自从首次提出问题以来,以下文本正文中的代码示例和基本细节/推理已经发生了重大变化.

上下文

我有一个令人难以置信的基本应用程序(底部的代码),它在自定义UIView的drawRect:方法中绘制单个elipses.当绘制的elipses的大小保持不变但自定义UIView的大小变大时,应用程序演示了性能上的差异:

小UIView

大UIView

我在运行iOS 4.3.5的iPod 4th Gen和运行iOS 5.1.1的iPad 1st Gen上运行应用程序,使用不同大小的自定义UIViews一系列.
下表显示了时间分析器仪器的结果:

时间分析器结果

以下仪器跟踪显示每个设备的两个极端的详细信息:

iOS 5.1.1 - (自定义UIView大小320 x 460)

iOS 5.1.1  - 自定义UIView大小320 x 460

iOS 5.1.1 - (自定义UIView大小960 x 1380)

iOS 5.1.1  - 自定义UIView大小960 x 1380

iOS 4.3.5 - (自定义UIView大小320 x 460)

iOS 4.3.5  - 自定义UIView大小320 x 460

iOS 4.3.5 - (自定义UIView大小960 x 1380)

iOS 4.3.5  - 自定义UIView大小960 x 1380

正如你所能(希望)在4个案例中的3个案例中我们得到了我们所期望的:大部分时间用于执行自定义UIViews drawRect:方法并且每个都持有10fps.
但是第四种情况显示了性能的下降,应用程序在仅绘制单个形状时难以保持7fps.在UIView的CALayer显示方法中,大部分时间用于复制内存,具体如下:

[CALayer display]>
[CALayer
_display ]> CABackingStoreUpdate>
CA :: Render :: ShmemBitmap :: copy_pixels(CA :: Render :: ShmemBitmap const*,CGSRegionObject*)>
memcpy $ VARIANT $ CortexA8

现在从这些数字中看出这里出现严重错误并不是天才.使用大小为960 x 1380的自定义UIView,iOS 4.3.5花费的时间超过复制内存的4倍,而不是绘制整个视图的内容.

现在,根据上下文,我再次提出我的问题:

为什么在iOS 4.3.5上,"大"(960 x 1380)自定义UIView执行CABackingStoreUpdate效率低下,如何提高绘图操作的性能?

很感谢任何形式的帮助.

我也在Apple Developer论坛上发布了这个问题.

真正的交易

现在,显然,为了这个问题,我已经将我的真正问题简化为最简单的可重现案例.我实际上试图为位于UIScrollView内的960 x 1380自定义UIView的一部分设置动画.

虽然我很欣赏任何人在未通过Quartz 2D达到他们想要的性能水平时引导任何人走向OpenGL ES的诱惑,但我要求任何采用这种方式的人至少提供一个解释,说明为什么Quartz 2D难以表现甚至是iOS 4.3.1中最基本的绘图操作,其中iOS 5.1.1没有问题.你可以想象我对为这个基石案重写一切的想法并不感到兴奋.这也适用于建议使用Core Animation的人.虽然为了简单起见,我在演示中使用了改变颜色的elipses(一个非常适合Core Animation的任务),但我实际想要执行的绘制操作是随着时间的推移而扩展的大量线条,一个绘图任务Quartz 2D非常适合(当它具有高性能时!).此外,再次,这将需要重写,并没有帮助解释这个奇怪的性能问题.

TViewController.m(标准视图控制器的实现)

#import "TViewController.h"
#import "TCustomView.h"

// VERSION 1 features the custom UIView the same size as the screen.
// VERSION 2 features the custom UIView nine times the size of the screen.
#define VERSION 2


@interface TViewController ()
@property (strong, nonatomic) TCustomView *customView;
@property (strong, nonatomic) NSTimer *animationTimer;
@end


@implementation TViewController

- (void)viewDidLoad
{
    // Custom subview.
    TCustomView *customView = [[TCustomView alloc] init];
    customView.backgroundColor = [UIColor whiteColor];
#if VERSION == 1
    customView.frame = CGRectMake(0.0f, 0.0f, 320.0f, 460.0f);
#else
    customView.frame = CGRectMake(0.0f, 0.0f, 960.0f, 1380.0f);
#endif

    [self.view addSubview:customView];

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    [customView addGestureRecognizer:singleTap];

    self.customView = customView;
}

#pragma mark - Timer Loop

- (void)handleTap:(UITapGestureRecognizer *)tapGesture
{
    self.customView.value = 0.0f;

    if (!self.animationTimer  || !self.animationTimer.isValid) {
        self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(animationLoop) userInfo:nil repeats:YES];
    }
}

#pragma mark - Timer Loop

- (void)animationLoop
{
    // Update model here. For simplicity, increment a single value.
    self.customView.value += 0.01f;

    if (self.customView.value >= 1.0f)
    {
        self.customView.value = 1.0f;
        [self.animationTimer invalidate];
    }

    [self.customView setNeedsDisplayInRect:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
}

@end
Run Code Online (Sandbox Code Playgroud)

-

TCustomView.h(自定义视图标题)

#import <UIKit/UIKit.h>

@interface TCustomView : UIView
@property (assign) CGFloat value;
@end
Run Code Online (Sandbox Code Playgroud)

-

TCustomView.m(自定义视图实现)

#import "TCustomView.h"

@implementation TCustomView

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Draw ellipses.
    CGContextSetRGBFillColor(context, self.value, self.value, self.value, 1.0f);
    CGContextFillEllipseInRect(context, rect);

    // Draw value itself.
    [[UIColor redColor] set];
    NSString *value = [NSString stringWithFormat:@"%f", self.value];
    [value drawAtPoint:rect.origin withFont:[UIFont fontWithName:@"Arial" size:15.0f]];
}

@end
Run Code Online (Sandbox Code Playgroud)

run*_*ark 5

由于iPod Touch第4代和iPad第1代都有类似的硬件(相同数量的内存/相同的GPU),它表明你看到的问题是由于iOS4中未优化的代码路径.

如果你看一下在iOS4上导致(负面)性能峰值的视图大小,它们都有一个长于1024的边.最初1024x1024是UIView的最大尺寸,虽然这个限制已被取消,但它完全是可能大于此的视图仅在iOS5及更高版本中变得高效.

我猜想你在iOS4中看到的多余内存复制是由于UIKit为大型UIView使用全尺寸内存缓冲区,但是在它们可以合成之前必须复制适当大小的内存块.而且在iOS5及更高版本中,他们要么删除了可以合成的图块大小的任何限制,要么改变了UIKit为这样大的UIViews呈现的方式.

在iOS4上解决这个瓶颈问题时,您可以尝试使用较小的UIViews来平铺您想要覆盖的区域.如果您将其构造为:

  Parent View - contains drawing and event related code
    Tile View 1 - contains drawRect
    ...
    Tile View n - contains drawRect

在每个切片视图中,您可以在调整图形上下文的变换后适当地要求父视图呈现其内容.这意味着您不必更改绘图代码,它只会被多次调用(这需要很小的开销,但请记住每次调用只会绘制整个视图的一部分).

请注意,父视图没有drawRect方法很重要 - 否则UIKit会认为你想直接绘制它,它会创建一个后备存储,从而使你回到相同的情况.

您还可以查看CATiledLayer - 这会为您提供平铺但异步; 意味着您的绘图代码必须处理从一个或多个后台线程执行.