问题简短:
LooksLike drawRect
本身(甚至是空的)会导致显着的性能瓶颈,具体取决于设备的分辨率 - 更大的屏幕是更糟糕的事情。
有没有办法加快重新绘制视图的内容?
更详细信息:
我在iOS上创建一个小型绘图应用程序 - 用户将手指移到显示屏上以绘制线条。
这背后的想法非常简单 - 当用户移动他的手指时touchesMoved
将更改累积到屏幕外缓冲区图像中,并使视图无效以将屏幕外缓冲区与视图内容合并。
简单的代码段可能如下所示:
@interface CanvasView : UIView
...
end;
@implementation CanvasView{
UIImage *canvasContentImage;
UIImage *bufferImage
CGContextRef drawingContext;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// prepare drawing to start
UIGraphicsBeginImageContext(canvasSize);
drawingContext = UIGraphicsGetCurrentContext();
...
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
// draw to bufferImage
CGContextMoveToPoint(drawingContext, prevPoint.x, prevPoint.y);
CGContextAddLineToPoint(drawingContext, point.x, point.y);
CGContextStrokePath(drawingContext);
...
[self setNeedDisplay];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
//finish drawing
UIGraphicsEndImageContext();
//merge canvasContentImage with bufferImage
...
}
-(void)drawRect:(CGRect)rect{
// draw bufferImage - merge it with current view's content
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, self.bounds, canvasContentImage.CGImage);
CGContextDrawImage(context, imageRect, bufferImage.CGImage);
...
}
我还实现了一个小助手类来计算fps率。
因此上述方法在非视网膜屏幕上的效果相当好,产生近60fps。然而fps率在视网膜屏幕上显着下降。例如,在iPad Retina上它大约是15-20 fps,这太慢了。
我认为第一个明显的原因是setNeedsDisplay
导致重绘整个屏幕,这是一个巨大的资源浪费。所以我转移到setNeedsDisplayInRect
只更新一个脏区域。令人惊讶的是,它没有改变任何与性能相关的东西(至少根据测量和视觉上没有任何明显的变化)。
所以我开始尝试不同的方法来找出瓶颈。当我评论出所有的绘图逻辑时,fps率仍然保持在15-20 - 看起来问题不在于绘制逻辑。
最后,当我完全注释掉drawRect
方法时,fps上升到60.不是说我只删除了实现,而是删除了声明。我不确定我的术语所以结果如下:
// -(void)drawRect:(CGRect)rect{
// // draw bufferImage - merge it with current view's content
// ...
// }
当我将所有绘图代码从drawRect
方法移动到touchMoved
方法时,更有趣的是它不会影响性能,但是相同数量的绘图/处理逻辑仍然在比较使用drawRect
方法的版本 - 甚至每次更新整个视图仍然给我60fps。
一个问题是如果没有drawRect,我就无法看到这些变化。
所以,我已经了解了预生成drawRect
方法的警告:
"仅覆盖drawRect:如果执行自定义绘图。一个空的 实施会对动画期间的表现产生负面影响。"
我的猜测是,每次触发自定义drawRect
时,系统都会创建并销毁图形上下文,导致"反过来影响性能"
所以问题是:
有没有办法加快drawRect
次来电,例如让系统重用资源从呼叫到drawRect
或其他什么?
如果这是一个死胡同,还有哪些其他可用于更新视图内容的方法?目前不能选择转向OpenGL,因为已经实现了很多代码/逻辑,并且需要花费很多精力来移植它。
我很乐意提供所需的任何其他信息。 并提前感谢任何建议!
修改
经过一些调查和实验后,我开始使用UIImageView及其图像属性动态更新视图内容。它提供了小的性能改进(绘图在19-22 fps时更稳定)。然而它仍然远远没有目标50-60fps。 有人认为我已经注意到,仅更新屏幕外缓冲区图像的脏部分确实有意义 - 不强制视图的内容更新,重绘屏幕外缓冲区的纯逻辑提供大约60 fps。 但是,一旦我尝试将更新后的图像附加到UIImageView.image属性来更新它的内容,fps就会降到19-22。这是合理的,因为分配属性会强制整个图像在视图侧重绘。
所以问题仍然存在 - 有没有办法只更新显示内容的视图的指定部分(UIImageView')
答案 0 :(得分:1)
花了好几天后,我意外地(至少对自己来说)结果。 我能够在视网膜iPad上达到30fps,这是现在可接受的结果。
对我有用的诀窍是:
UIImageView
UIImageView.image
属性更新内容 - 与使用setNeedsDisplay
/ setNeedsDisplayInRect
方法的普通UIView相比,它提供了更好的结果。[self performSelector:@selector(setImage:) withObject:img afterDelay:0];
而不是UIImageView.image = img
将更新的图像设置为UIImageView。 最后一点对我来说是一种咒语,但是它提供了最小的必要帧速率(即使原始图像每帧都重新绘制,不仅是脏区域)。
我的猜测为什么在我的情况下,为什么performSelector有助于获得更新视图的fps,因为视图队列上的调度setImage
可以优化触摸事件处理期间可能发生的内部空闲。
这只是我的猜测,如果有人能提供相关解释,我很乐意在这里发布或接受答案。