UIBezierPath内存问题

时间:2014-06-03 15:48:08

标签: ios objective-c cocoa-touch memory-management uibezierpath

我正在创建一个应用程序,在平铺视图中显示大量蓝图,并在图像上叠加各种注释(形状)。我正在使用UIBezierPaths绘制这些形状(主要是椭圆,线和多边形),但它使用了大量的内存。这是我的第一个应用程序,所以任何建议都将受到赞赏。

这是绘制省略号的UIView子类的示例。我知道使用图像视图来显示路径是没有必要的,但它似乎比替代方案表现更好。我的希望是,一旦我拥有UIImage,我就可以处理路径使用的内存,但正如您在下面的截图中看到的那样,它并没有真正起作用。

CGSize size = CGSizeMake(self.frame.size.width+self.lineWeight,
                          self.frame.size.height+self.lineWeight);

UIGraphicsBeginImageContext(size);

UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[self.fillColor setFill];
[path closePath];
[path fill];


CGRect strokeRect = CGRectMake(self.lineWeight/2.0, self.lineWeight/2.0,
                               size.width-self.lineWeight*2.0, size.height-self.lineWeight*2.0);
UIBezierPath *strokePath = [UIBezierPath bezierPathWithOvalInRect:strokeRect];
strokePath.lineWidth = self.lineWeight;
[self.lineColor setStroke];
[strokePath stroke];
[strokePath closePath];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imageView];

这是仪器的截图,显示了显示26个形状的图像时的内存使用情况,主要是省略号。

enter image description here

我展示的一些蓝图可能有多达500个形状,所以我真的可以使用一些建议来更好地实现这一目标。谢谢!

2 个答案:

答案 0 :(得分:2)

这里的内存问题实际上并不是UIBezierPath,而是UIImage个对象(每个形状每个像素占用四个字节)。

如果这是在UIView子类中,那么只需将此绘图代码放在drawRect方法中并绘制到那里,而不是绘制到图片。或者使用此UIBezierPath创建CAShapeLayer,然后添加该图层和视图图层的子图层。

但除非你必须这样做,否则不要创建UIImage个对象,因为这会占用大量内存。


我对这三种方法进行了基准测试,并以一个相当极端的例子对其进行了基准测试,其中包含5,000个重叠椭圆,每个椭圆的大小介于20x20和40x40像素之间。显然,这是一个荒谬的例子,它们彼此重叠,但我只是试图在极端情况下压力测试内存消耗。此外,在我的例子中,我只渲染椭圆,但你可以明显地定制它以适应你想要绘制的任何形状。

无论如何,我的结果如下:

  1. 创建单个UIImage个对象,每个对象绘制一个椭圆形状。显然,这将相当积极地使用内存,每个图像需要每像素4个字节。在我的例子中,这消耗了92mb。 "添加椭圆"的代码例程看起来像这样:

    UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGRect strokeRect = CGRectMake(self.lineWeight/2.0, self.lineWeight/2.0,
                                   self.frame.size.width-self.lineWeight*2.0, self.frame.size.height-self.lineWeight*2.0);
    
    UIBezierPath *strokePath = [UIBezierPath bezierPathWithOvalInRect:strokeRect];
    CGContextAddPath(context, strokePath.CGPath);
    [self.lineColor setStroke];
    [self.fillColor setFill];
    CGContextSetLineWidth(context, self.lineWeight);
    CGContextDrawPath(context, kCGPathEOFillStroke);
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.frame];
    imageView.image = image;
    [self addSubview:imageView];
    
  2. 为每个形状创建单独的UIView子类实例,每个UIView子类都有自己的drawRect方法。虽然我期待在这里看到一些显着的节省,但它是适度的,消耗94.5%的内存,总共87mb:

    - (void)drawRect:(CGRect)rect
    {
        CGRect strokeRect = CGRectMake(self.lineWeight/2.0, self.lineWeight/2.0,
                                       rect.size.width-self.lineWeight*2.0, rect.size.height-self.lineWeight*2.0);
    
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:strokeRect];
    
        [self.lineColor setStroke];
        [self.fillColor setFill];
        path.lineWidth = self.lineWeight;
    
        [path fill];
        [path stroke];
    }
    

    似乎iOS必须维护单个UIView子类实例的位图表示(即使shouldRasterize已关闭)。

  3. 为要绘制的对象创建模型结构,然后在超级视图中使用单个drawRect方法呈现它们,以显示所有这些椭圆。这样可以大大提高内存效率,在渲染这些5,000个椭圆时耗费3.5mb,与原始方法相比,每个形状只有3.8 UIImage对象的内存只有3.8%。

    - (void)drawRect:(CGRect)rect
    {
        [self.lineColor setStroke];
        [self.fillColor setFill];
    
        for (NSValue *rectValue in self.ovals) {
            CGRect frame = [rectValue CGRectValue];
            CGRect strokeRect = CGRectMake(frame.origin.x + self.lineWeight/2.0, frame.origin.y + self.lineWeight/2.0,
                                           frame.size.width - self.lineWeight*2.0, frame.size.height - self.lineWeight*2.0);
            UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:strokeRect];
            path.lineWidth = self.lineWeight;
            [path fill];
            [path stroke];
        }
    }
    
  4. 我怀疑你可以通过创建快照然后删除单个实例来提高第一种方法的效率,但第三种方法似乎更合乎逻辑。

答案 1 :(得分:0)

有一个很好的解决方案,我不能以代码的形式给你完整的具体答案,但我可以给你一个抽象的起点。您必须考虑使用Programming Design Patterns,并为您提供必须采取的情况

<强> Flyweight pattern

什么时候使用Flyweight模式?

如果满足以下所有条件,您自然会考虑使用它:

  1. 您的应用使用了大量对象。
  2. 将对象保留在内存中会影响 记忆表现。
  3. 大多数对象的独特状态(外在的 ())可以外化和轻量化。
  4. ....
  5. 上面的文字来自书籍:Pro Objective-C Design Patterns for iOS

    阅读并享受cording ...