最少CPU密集的方式来经常和重复绘制许多视图

dub*_*eat 2 iphone performance quartz-graphics ipad ios

这是我一直在离开并回归一段时间的问题.我从来没有真正解决过这个问题.

我一直在尝试使用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];

        });



    });


}
Run Code Online (Sandbox Code Playgroud)

当尝试同时更新超过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);

}
Run Code Online (Sandbox Code Playgroud)

Stu*_*art 5

我认为你应该做的第一件事就是CGPath使用CGLayer对象在屏幕外缓冲你的段(当前由对象表示).来自文档:

图层适用于以下内容:

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

  • 重复绘图.例如,您可能希望创建一个由反复绘制的相同项组成的模式.将项目绘制到图层,然后重复绘制图层,如图12-1所示.您重复绘制的任何Quartz对象(包括CGPath,CGShading和CGPDFPage对象)都可以从将其绘制到CGLayer中时提高性能.请注意,图层不仅适​​用于屏幕绘图; 您可以将它用于非面向屏幕的图形上下文,例如PDF图形上下文.

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

- (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context;
Run Code Online (Sandbox Code Playgroud)

然后,它将成为委托的工作,返回现有的CGLayerRef,或者如果它还不存在,则创建它.由于CGLayer只能从内部产生drawRect:,此委托方法应该被调用PieViewdrawRect:方法.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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)



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;
}
Run Code Online (Sandbox Code Playgroud)



因此,对于每个饼图,创建一个新的PieView并设置它的委托.当你需要更新一个馅饼,更新PieViewprogress属性并调用setNeedsDisplay.

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



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