使用 CGImage / CGLayer 在另一个线程中绘图

Posted

技术标签:

【中文标题】使用 CGImage / CGLayer 在另一个线程中绘图【英文标题】:Drawing in another thread with CGImage / CGLayer 【发布时间】:2013-05-14 13:48:23 【问题描述】:

我有自定义 UICollectionViewCell 子类,我在其中使用剪切、描边和透明度进行绘制。它在 Simulator 和 iPhone 5 上运行良好,但在旧设备上存在明显的性能问题。

所以我想把耗时的绘图移到后台线程。由于 -drawRect 方法总是在主线程上调用,我最终将绘制的上下文保存到 CGImage(原始问题包含使用 CGLayer 的代码,但正如 Matt Long 指出的那样,它有点过时了)。

这是我在这个类中的 drawRect 方法的实现:

-(void)drawRect:(CGRect)rect 

    CGContextRef ctx = UIGraphicsGetCurrentContext();
    if (self.renderedSymbol != nil) 
        CGContextDrawImage(ctx, self.bounds, self.renderedSymbol);
    

定义此 renderSymbol 属性的渲染方法:

- (void) renderCurrentSymbol 

    [self.queue addOperationWithBlock:^

// creating custom context to draw there (contexts are not thread safe)
        CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
        CGContextRef ctx = CGBitmapContextCreate(nil, self.bounds.size.width, self.bounds.size.height, 8, self.bounds.size.width * (CGColorSpaceGetNumberOfComponents(space) + 1), space, kCGImageAlphaPremultipliedLast);
        CGColorSpaceRelease(space);

// custom drawing goes here using 'ctx' context

// then saving context as CGImageRef to property that will be used in drawRect
        self.renderedSymbol = CGBitmapContextCreateImage(ctx);

// asking main thread to update UI    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^
            [self setNeedsDisplayInRect:self.bounds];
        ];

        CGContextRelease(ctx);

    ];

这个设置在主线程上完美运行,但是当我用 NSOperationQueue 或 GCD 包装它时,我得到了很多不同的“invalid context 0x0”错误。应用程序本身不会崩溃,但不会发生绘图。我想发布自定义创建的 CGContextRef 有问题,但我不知道该怎么办。

这是我的财产声明。 (我尝试使用原子版本,但没有帮助)

@property (nonatomic) CGImageRef renderedSymbol;
@property (nonatomic, strong) NSOperationQueue *queue;
@property (nonatomic, strong) NSString *symbol; // used in custom drawing

属性的自定义设置器/获取器:

-(NSOperationQueue *)queue 
    if (!_queue) 
        _queue = [[NSOperationQueue alloc] init];
        _queue.name = @"Background Rendering";
    
    return _queue;
   
-(void)setSymbol:(NSString *)symbol 
    _symbol = symbol;
    self.renderedSymbol = nil;
    [self setNeedsDisplayInRect:self.bounds];


-(CGImageRef) renderedSymbol 
    if (_renderedSymbol == nil) 
        [self renderCurrentSymbol];
    
    return _renderedSymbol;

我能做什么?

【问题讨论】:

您的第一个 drawInRect 最终会绘制一个 NULL-CGLayer,因为您在 renderedSymbol 中异步调用 renderCurrentSymbol 但会立即返回 _renderedSymbol 您是否尝试过在释放 CGLayer 之前不执行 CGContextRelease(ctx)(大概在 dealloc 中?) @MikePollard 释放上下文是正确的,如果你不释放上下文就会有内存泄漏。 是的,我不是建议不释放它,而是在dealloc中释放它,这大概是释放CGLayer的地方,否则此刻正在泄漏。 invalid context 0x0 通常意味着您的 CGContext 为 nil 或其大小为零。你应该为这两件事调试你的程序。 【参考方案1】:

您是否注意到您引用的 CGLayer 上的文档自 2006 年以来一直没有更新?您认为 CGLayer 是正确解决方案的假设是不正确的。 Apple 几乎放弃了这项技术,您可能也应该这样做:http://iosptl.com/posts/cglayer-no-longer-recommended/ 使用 Core Animation。

【讨论】:

哇。谢谢!在 CGLayer 之前,我尝试通过将上下文保存到 CGImageRef 来做同样的事情(就像在Stack Overflow 上的这个问题中解释的那样),但它也不起作用。仍然收到此 0x0 无效上下文错误 我不会说 CGLayer 不是正确的解决方案,它“可能”不是,但它可以。 @RinatKhanov 我建议你从更高的层面提出你的问题,看看人们认为什么是最好的方法。从您的问题来看,在我看来,您可以只合成多个 Core Animation 层并让它为您完成所有艰苦的工作。我创建了许多自定义集合视图单元格,它们使用了诸如剪切描边和不透明度之类的东西。您可能想查看 CALayer 的 shouldRasterize 属性。它将提高绘图性能。您甚至可以考虑使用图像而不是以编程方式绘图,因为这也可以提高性能。 @MattLong 在集合视图中,我需要上千个带有文本符号或图像的圆角矩形(带有描边和透明角)。我在应用程序的其他部分需要该符号作为文本表示,因此使用图像不是我特定情况的解决方案。我试图关闭那个圆角矩形(用作背景),但令人惊讶的是应用程序在旧设备上滚动时感觉很慢。这就是为什么我尝试处理大量渲染到后台线程的原因。我去看看CALayer,谢谢。【参考方案2】:

通过 Mind Snacks 使用令人惊叹的第三方库解决了问题 — MSCachedAsyncViewDrawing。

【讨论】:

以上是关于使用 CGImage / CGLayer 在另一个线程中绘图的主要内容,如果未能解决你的问题,请参考以下文章

来自存储在 iCloud 上的图像的 CGImage 看起来不对

撤消 CGLayer 的重做问题

在方法drawRect之外绘制CGLayer:更快?

如何提高CGLayer的绘图质量

CGLayer和CALayer有啥区别和兼容性?

将屏幕外 CGLayer 传送到当前上下文时出现锯齿状路径