I am using CATiledLayer as backing layer for my UIView, which I have put inside UIScrollView. In init method of my view I am creating CGPathRef object which draws simple line. When I am trying to draw this path inside drawLayer:inContext it occasionally crashes with EXEC_BAD_ACCESS (rarely) when I am scrolling / zooming.
The code is very simple, I am using only standard CG* functions:
- (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];
}
UPDATE: I have noiced that this problem exists only on iOS 5, it works fine on 4.3
I ran into a similar issue when attempting to draw cached CGPath objects on a custom MKOverlayView.
The crash may occur because a CGPath can't be simultaneously drawn on multiple threads – it's an opaque class which (as specified in the documentation) contains a pointer to the current point in its points array. Two or more threads iterating over this array simultaneously while they draw it could lead to undefined behavior and a crash.
I worked around this by copying the CGPath object into each drawing thread (contained within a mutex lock to prevent incomplete copying):
//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);
If you're concerned about the memory overhead of doing a copy on each thread, you can also work directly on the cached CGPath objects, but the mutex will have to remain locked during the whole drawing process (which kind of defeats the purpose of threaded drawing):
//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);
I'll qualify my answer by saying that I'm not an expert on multithreaded drawing with Quartz, only that this approach solved the crashes in my scenario. Good luck!
UPDATE: I revisited this code now that iOS 5.1.0 is out and it looks like the root cause of the issue may have actually been a bug in Quartz in iOS 5.0.x. When testing on iOS 5.1.0 with the CGPathCreateCopy() and mutex calls removed, I'm seeing none of the crashes experienced on 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
Since chances are we'll be supporting iOS 5.0.x for a while, it won't hurt to keep the mutex in your code (other than a slight performance hit), or simply run a version check before drawing.