为什么在 iOS 4.3.5 上,“大”(960 x 1380)自定义 UIView 执行 CABackingStoreUpdate 的效率如此低下,我该如何提高绘图操作的性能?
不完全确定我的意思?继续阅读...
笔记:
随着我对这个问题的理解不断发展,这个问题也在不断发展。因此,问题本身是相似的,但自首次提出问题以来,以下正文中的代码示例和基本细节/推理发生了显着变化。
语境
我有一个非常基本的应用程序(底部的代码),它在 drawRect: 自定义 UIView 的方法中绘制单个省略号。当绘制的椭圆的大小保持不变但自定义 UIView 的大小变大时,应用程序演示了性能差异:
我在运行 iOS 4.3.5 的第 4 代 iPod 和运行 iOS 5.1.1 的第 1 代 iPad 上使用不同大小的自定义 UIView 多次运行该应用程序。
下表显示了从时间分析器仪器获取的结果:
以下仪器轨迹显示了每个设备的两个极端的详细信息:
iOS 5.1.1 - (自定义 UIView 大小 320 x 460)
iOS 5.1.1 - (自定义 UIView 尺寸 960 x 1380)
iOS 4.3.5 - (自定义 UIView 大小 320 x 460)
iOS 4.3.5 - (自定义 UIView 尺寸 960 x 1380)
正如您(希望)所见,在 4 个案例中的 3 个案例中,我们得到了我们期望的结果:大部分时间都花在了执行自定义 UIViews drawRect: 方法上,每个案例都保持 10fps。
但是第四个案例显示了应用程序在仅绘制单个形状时难以保持 7fps 的性能下降。在 UIView 的 CALayer 的显示方法中,大部分时间都花在了复制内存上,具体来说:
[CALayer 显示] >
[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 forums上发布了这个问题。
真正的交易
现在,显然,为了这个问题,我已经将我的真正问题简化为最简单的可重现案例。我实际上是在尝试为位于 UIScrollView 内的 960 x 1380 自定义 UIView 的一部分设置动画。
虽然我很欣赏当他们没有通过 Quartz 2D 达到他们想要的性能水平时引导任何人使用 OpenGL ES 的诱惑,但我要求任何采取这条路线的人至少解释一下为什么 Quartz 2D 甚至在iOS 4.3.5 上最基本的绘图操作,iOS 5.1.1 没有问题。正如你可以想象的那样,我对为这个基石案例重写所有内容的想法并不感到兴奋。这也适用于建议使用 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
-
TCustomView.h(自定义视图标题)
#import <UIKit/UIKit.h>
@interface TCustomView : UIView
@property (assign) CGFloat value;
@end
-
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