Почему на iOS 4.3.5 пользовательский UIView выполняет "большой" (960 x 1380) тактический CABackingStoreUpdate и как я могу улучшить производительность операций рисования?
Не совсем уверен, что я имею в виду? Читайте дальше...
Примечание:
По мере того как мое понимание этой проблемы эволюционировало, возникает и этот вопрос. В результате сам вопрос схож, но пример кода и основные сведения/рассуждения в следующем тексте существенно изменились, поскольку вопрос был задан впервые.
Контекст
У меня есть невероятно базовое приложение (код внизу), который рисует один elipses в методе drawRect: пользовательский UIView. Приложение демонстрирует разницу в производительности, когда размер элипсов, нарисованных, остается тем же, но размер пользовательского UIView становится больше:
Я запустил приложение на iPod 4th Gen под управлением iOS 4.3.5 и iPad 1-го поколения, работающего под управлением iOS 5.1.1, с использованием пользовательских 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)
Как вы можете (надеюсь) увидеть в 3 из 4 случаев, мы получаем то, что ожидаем: большая часть времени была потрачена на выполнение пользовательских методов UIViews drawRect: и каждый из них - 10 кадров в секунду.
Но в четвертом случае демонстрируется плюммет в производительности, при этом приложение изо всех сил пытается удерживать 7 кадров в секунду, а только рисует одну фигуру. Большинство времени было потрачено на копирование памяти во время отображения UIView CALayer, в частности:
[CALayer display] >
[CALayer _display] >
CABackingStoreUpdate >
CA:: Render:: ShmemBitmap:: copy_pixels (CA:: Render:: ShmemBitmap const *, CGSRegionObject *) >
тетср $ВАРИАНТ $CortexA8
Теперь не нужно гениально видеть из цифр, что здесь что-то серьезно не так. С пользовательским UIView размером 960 x 1380 iOS 4.3.5 тратит в 4 раза больше времени на копирование памяти, чем на рисование всего содержимого представления.
Вопрос
Теперь, учитывая контекст, я снова задаю свой вопрос:
Почему на iOS 4.3.5 пользовательский UIView выполняет "большой" (960 x 1380) тактический CABackingStoreUpdate и как я могу улучшить производительность операций рисования?
Любая помощь очень ценится.
Я также разместил этот вопрос на форумах разработчиков Apple.
Реальная сделка
Теперь, очевидно, я уменьшил свою реальную проблему до простейшего воспроизводимого случая ради этого вопроса. Я на самом деле пытаюсь оживить часть пользовательского UIView 960 x 1380, который находится внутри UIScrollView.
Несмотря на то, что я ценю соблазн направить кого-либо в сторону OpenGL ES, когда они не достигают уровня производительности, которого они хотят с помощью Quartz 2D, я прошу, чтобы любой, кто берет этот маршрут, хотя бы объяснил, почему Quartz 2D борется с выполняйте даже самые основные операции рисования на iOS 4.3.5, где iOS 5.1.1 не имеет проблем. Как вы можете себе представить, я не в восторге от идеи переписывать все для этого краеугольного камня. Это также относится к людям, предлагающим использовать Core Animation. Хотя я использовал элипсы, меняющие цвет (задача, идеально подходящая для Core Animation) в демонстрации ради простоты, операции рисования, которые я действительно хотел бы выполнить, - это большое количество строк, расширяющихся со временем, задача рисования Кварц 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