【问题标题】:CATiledLayer crashes when drawing previously prepared CGPath绘制先前准备好的 CGPath 时,CATiledLayer 崩溃
【发布时间】:2011-12-16 11:40:23
【问题描述】:

我正在使用 CATiledLayer 作为我的 UIView 的支持层,我将它放在 UIScrollView 中。在我的视图的 init 方法中,我正在创建绘制简单线条的 CGPathRef 对象。当我试图在 drawLayer:inContext 中绘制这条路径时,它偶尔会在我滚动/缩放时与 EXEC_BAD_ACCESS 崩溃(很少)。

代码很简单,我只使用标准的 CG* 函数:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
        tiledLayer.levelsOfDetail = 10;
        tiledLayer.levelsOfDetailBias = 5;
        tiledLayer.tileSize = CGSizeMake(512.0, 512.0);

        CGMutablePathRef mutablePath = CGPathCreateMutable();
        CGPathMoveToPoint(mutablePath, nil, 0, 0);
        CGPathAddLineToPoint(mutablePath, nil, 700, 700);
        path = CGPathCreateCopy(mutablePath);
        CGPathRelease(mutablePath);
    }
    return self;
}

+ (Class) layerClass {
    return [CATiledLayer class];
}

- (void) drawRect:(CGRect)rect {
}

- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
    CGContextFillRect(ctx, self.bounds);

    CGContextSetLineWidth(ctx, 5);

    CGContextAddPath(ctx, path);
    CGContextDrawPath(ctx, kCGPathStroke);
}

- (void)dealloc {
    [super dealloc];
}

更新: 我注意到这个问题只存在于 iOS 5 上,它在 4.3 上运行良好

【问题讨论】:

  • 启用 NSZombieEnabled 后,调试 EXEC_BAD_ACCESS 很容易,它会显示正在调用的已释放变量。
  • 但是在这段代码中不可能对内存做坏事,无论如何它只会发生在 CATiledLayer 和多线程中。
  • 这是线程共享的类还是每个线程都有自己的实例?
  • 视图是在UI线程中创建的,但是drawayer方法是从不同的线程调用的,因为它被CATiledLayer使用,这就是它的工作方式。 BAD_ACCESS 总是出现在这一行:CGContextAddPath(ctx, path)

标签: iphone ios ipad catiledlayer cgpath


【解决方案1】:

我在尝试在自定义 MKOverlayView 上绘制缓存的 CGPath 对象时遇到了类似的问题。

可能会发生崩溃,因为 CGPath 不能同时在多个线程上绘制 - 它是一个不透明的类(如文档中所述)包含指向其点数组中当前点的指针。两个或多个线程在绘制此数组时同时迭代此数组可能会导致未定义的行为和崩溃。

我通过将 CGPath 对象复制到每个绘图线程(包含在互斥锁中以防止复制不完整)来解决此问题:

//lock the object's cached data
pthread_mutex_lock(&cachedPathMutex);
//get a handle on the previously-generated CGPath (myObject exists on the main thread)
CGPathRef myPath = CGPathCreateCopy(myObject.cachedPath);
//unlock the mutex once the copy finishes
pthread_mutex_unlock(&cachedPathMutex);

// all drawing code here
CGContextAddPath(context, myPath);
...
...
CGPathRelease(myPath);

如果您担心在每个线程上进行复制的内存开销,您也可以直接在缓存的 CGPath 对象上工作,但互斥锁必须在整个绘制过程中保持锁定(哪种方式会破坏螺纹绘图的目的):

//lock the object's cached data
pthread_mutex_lock(&cachedPathMutex);

//get a handle on the previously-generated CGPath (myObject exists on the main thread)
CGPathRef myPath = myObject.cachedPath;

// draw the path in the current context
CGContextAddPath(context, myPath);
...
...

//and unlock the mutex
pthread_mutex_unlock(&cachedPathMutex);

我将通过说我不是使用 Quartz 进行多线程绘图的专家来限定我的答案,只是这种方法解决了我的场景中的崩溃问题。祝你好运!

更新: 既然 iOS 5.1.0 已经发布,我重新访问了这段代码,看起来问题的根本原因实际上可能是 iOS 5.0.x 中的 Quartz 中的一个错误。在移除 CGPathCreateCopy() 和 mutex 调用的 iOS 5.1.0 上进行测试时,我没有看到 iOS 5.0.x 上发生的任何崩溃。

//get a handle on the previously-generated CGPath (myObject exists on the main thread)
CGPathRef myPath = myObject.cachedPath;

// all drawing code here
CGContextAddPath(context, myPath);
...
...
//drawing finished

由于我们可能会在一段时间内支持 iOS 5.0.x,因此将互斥锁保留在您的代码中(除了轻微的性能影响)或在绘图前简单地运行版本检查不会有什么坏处。

【讨论】:

  • 感谢您的回答,它解释了很多!
  • 感谢您 - 它解决了与我的自定义 MKOverlayView 类似的问题。如果我对并发编程指南的阅读是正确的,那么对串行队列使用 GCD 样式的 dispatch_synch 可能比使用互斥锁更有效。不得不创建路径的副本感到羞耻-我想问题是在串行队列中还是在锁中创建副本是否比每次都从头开始重新创建路径要快。我想,答案取决于路径创建代码的复杂性/效率。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-26
  • 2011-04-09
  • 1970-01-01
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多