至少CPU密集的方式经常和;反复绘制很多观点

时间:2011-11-07 15:48:42

标签: iphone ios performance ipad quartz-graphics

这是我一直在离开的问题,现在又回来了。我从未真正解决过这个问题。

我一直在尝试使用CADisplayLink动态绘制饼图样式进度。当我同时更新1到4个uiviews时,我的代码工作正常。当我添加不止于此时,馅饼的绘图变得非常生涩。

我想解释一下我一直在尝试的事情,希望有人能指出效率低下并提出更好的绘图方法。

我创建了16个uiviews并为每个子视图添加了一个CAShapeLayer子视图。这是我想要绘制饼图的地方。

我预先计算代表0到360度圆的360 CGPath并将它们存储在一个数组中以尝试提高性能。

在主视图中,我启动了一个displaylink,遍历所有其他视图,计算它应显示的完整饼图的数量,然后找到正确的路径并将其分配给我的shapelayer。

-(void)makepieslices
{
    pies=[[NSMutableArray alloc]initWithCapacity:360];
    float progress=0;
    for(int i=0;i<=360;i++)

    {
        progress= (i* M_PI)/180;
        CGMutablePathRef thePath = CGPathCreateMutable();
        CGPathMoveToPoint(thePath, NULL, 0.f, 0.f);
        CGPathAddLineToPoint(thePath, NULL, 28, 0.f);
        CGPathAddArc(thePath, NULL, 0.f,0.f, 28, 0.f, progress, NO);
        CGPathCloseSubpath(thePath);
        _pies[i]=thePath;


    }



}

- (void)updatePath:(CADisplayLink *)dLink {

    for (int idx=0; idx<[spinnydelegates count]; idx++) {

        id<SyncSpinUpdateDelegate> delegate = [spinnydelegates objectAtIndex:idx];
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [delegate updatePath:dLink];
        });

    }






}

- (void)updatePath:(CADisplayLink *)dLink {

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        currentarc=[engineref getsyncpercentForPad:cid pad:pid];

        int progress;

        progress = roundf(currentarc*360);

        dispatch_async(dispatch_get_main_queue(), ^{
            shapeLayer_.path = _pies[progress];

        });



    });


}

当尝试同时更新超过4或5个馅饼时,这项技术直接用于我。同时16个屏幕更新听起来应该真的不是我对ipad的大交易。所以这让我觉得我做了一件非常根本错误的事情。

我真的很感激,如果有人能告诉我为什么这种技术会导致抖动屏幕更新,并且如果他们可以提出一种不同的技术,我可以进行调查,这将允许我顺利地执行16个同时的shapelayer更新。

编辑只是为了让您了解性能有多糟糕,当我有16个馅饼时,绘制cpu的比例高达20%

* 编辑*

这是基于studevs的建议,但我没有看到任何被绘制的内容。 segmentLayer是一个CGLayerRef,作为我的pieview的属性。

-(void)makepies
{

    self.layerobjects=[NSMutableArray arrayWithCapacity:360];

    CGFloat progress=0;

    CGContextRef context=UIGraphicsGetCurrentContext();

    for(int i =0;i<360;i++)
    {
        progress= (i*M_PI)/180.0f;

        CGLayerRef segmentlayer=CGLayerCreateWithContext(context, CGSizeMake(30, 30), NULL);
        CGContextRef layerContext=CGLayerGetContext(segmentlayer);
        CGMutablePathRef thePath = CGPathCreateMutable();
        CGPathMoveToPoint(thePath, NULL, 0.f, 0.f);
        CGPathAddLineToPoint(thePath, NULL, 28, 0.f);
        CGPathAddArc(thePath, NULL, 0.f,0.f, 28, 0.f, progress, NO);
        CGPathCloseSubpath(thePath);

        [layerobjects addObject:(id)segmentlayer];
        CGLayerRelease(segmentlayer);
    }

}



-(void)updatePath
{
    int progress;

    currentarc=[engineref getsyncpercent];
    progress = roundf(currentarc*360);


    //shapeLayer_.path = _pies[progress];

    self.pieView.segmentLayer=(CGLayerRef)[layerobjects objectAtIndex:progress];

    [self.pieView setNeedsDisplay];


}

-(void)drawRect:(CGRect)rect
{

    CGContextRef context=UIGraphicsGetCurrentContext();

    CGContextDrawLayerInRect(context, self.bounds, segmentLayer);

}

4 个答案:

答案 0 :(得分:5)

我认为您应该做的第一件事就是使用CGPath对象在屏幕外缓冲您的细分(目前由CGLayer个对象表示)。来自文档:

  

图层适用于以下内容:

     
      
  • 您打算重复使用的高质量离线渲染图纸。   例如,您可能正在构建场景并计划重用该场景   背景。将背景场景绘制到图层然后绘制   层,无论何时需要它。一个额外的好处是你不需要   知道要绘制的颜色空间或设备相关信息   层

  •   
  • 重复绘图。例如,您可能想要创建一个   由反复绘制的相同项目组成的模式。画出来   将项目添加到图层然后重复绘制图层,如图所示   12-1。您重复绘制的任何Quartz对象 - 包括CGPath,   如果,CGShading和CGPDFPage对象可以从改进的性能中受益   你把它绘制成CGLayer。请注意,图层不仅适​​用于屏幕   画画;你可以将它用于非图形上下文   面向屏幕的,例如PDF图形上下文。

  •   

创建一个绘制饼图的UIView子类。为该饼图的当前进度提供一个实例变量,并覆盖drawRect:以绘制表示该进度的图层。视图需要首先获得所需CGLayer对象的引用,因此使用方法实现委托:

- (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context;

然后它将成为委托的工作,以返回现有的CGLayerRef,或者如果它还不存在,则创建它。由于CGLayer只能在drawRect:内创建,因此应从PieView的{​​{1}}方法调用此委托方法。 drawRect:应该是这样的:

PieView

PieView.h

#import <UIKit/UIKit.h> #import <QuartzCore/QuartzCore.h> @class PieView; @protocol PieViewDelegate <NSObject> @required - (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context; @end @interface PieView : UIView @property(nonatomic, weak) id <PieViewDelegate> delegate; @property(nonatomic) NSInteger progress; @end

PieView.m



您的#import "PieView.h" @implementation PieView @synthesize delegate, progress; - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGLayerRef segmentLayer = [delegate pieView:self segmentLayerForProgress:self.progress context:context]; CGContextDrawLayerInRect(context, self.bounds, segmentLayer); } @end 代表(很可能是您的视图控制器)然后实现:

PieView



因此,对于每个饼图,创建一个新的NSString *const SegmentCacheKey = @"SegmentForProgress:"; - (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context { // First, try to retrieve the layer from the cache NSString *cacheKey = [SegmentCacheKey stringByAppendingFormat:@"%d", progress]; CGLayerRef segmentLayer = (__bridge_retained CGLayerRef)[segmentsCache objectForKey:cacheKey]; if (!segmentLayer) { // If the layer hasn't been created yet CGFloat progressAngle = (progress * M_PI) / 180.0f; // Create the layer segmentLayer = CGLayerCreateWithContext(context, layerSize, NULL); CGContextRef layerContext = CGLayerGetContext(segmentLayer); // Draw the segment CGContextSetFillColorWithColor(layerContext, [[UIColor blueColor] CGColor]); CGContextMoveToPoint(layerContext, layerSize.width / 2.0f, layerSize.height / 2.0f); CGContextAddArc(layerContext, layerSize.width / 2.0f, layerSize.height / 2.0f, layerSize.width / 2.0f, 0.0f, progressAngle, NO); CGContextClosePath(layerContext); CGContextFillPath(layerContext); // Cache the layer [segmentsCache setObject:(__bridge_transfer id)segmentLayer forKey:cacheKey]; } return segmentLayer; } 并设置它的委托。当您需要更新饼图时,请更新PieView的{​​{1}}媒体资源并致电PieView

我在这里使用progress,因为存储了大量图形,并且可能占用大量内存。您还可以限制绘制的细分数量 - 100可能很多。此外,我同意其他评论/答案,您可能会尝试更少地更新视图,因为这会消耗更少的CPU和电池电量(可能不需要60fps)。



我在iPad(第一代)上对这种方法进行了一些粗略的测试,并设法以30fps的速度获得超过50个馅饼。

答案 1 :(得分:3)

  

dubbeat:... CADisplayLink ...

     
    

贾斯汀:你需要以显示器的刷新率绘制吗?

         
      

dubbeat:饼图绘制的进度应该代表mp3播放进度的进度,所以我猜测显示刷新率至少为。

    
  

这比必要的要快得多,除非你试图展示一些真正的,真正非常奇特的可视化器,如果你的微调器的半径是28pt,这是不太可能的。此外,没有理由比绘制频率更快地绘制。

一个副作用是你的微调器的超级视图也可能以这么高的频率更新。如果您可以使微调器视图不透明,那么您可以减少超级视图的过度绘制(如果有的话,还可以减少子视图)。

对于真正快速的桌面游戏来说,60fps是一个很好的数字。对于装饰/进度条,它远远超过必要。

试试这个:

  • 不使用CADisplayLink,而是使用标准视图系统
  • 在主运行循环中使用NSTimer,以8 Hz *
  • 的频率开始
  • 调整计时器以品尝

然后告诉我们这是否足够快。

*定时器回调调用[spinner setNeedsDisplay]

答案 2 :(得分:0)

嗯,通过预先组装背景视图,捕获图像,然后在图像视图中使用图像作为背景,可以实现性能提升。您可以通过捕获图表中“相对静态”部分的视图,仅在必要时更新该静态视图。

答案 3 :(得分:0)

将360个圆弧片段存储为纹理,并使用OpenGL为序列设置动画。