问题
I use a CATiledLayer in my app, and as a result, drawing of that layer is done in a background thread. That is, the drawLayer:inContext: method of my delegate is called from a background thread. The setNeedsDisplayInRect used to invalidate parts of the CATiledLayer is always called from the main thread.
Because they are independent threads, it occasionally happens that the setNeedsDisplayInRect is called while the background thread is in the drawLayer:inContext method. I have noticed that the setNeedsDisplayInRect is ignored in that situation (drawLayer:inContext is not called again).
I have logged a bug to Apple, because I think that is not correct. But I have a hard time figuring out how to work around this situation. Do you have good ideas?
EDIT:
I tested Stanislaw's answer using the following code:
- (void) setNeedsDisplayInRect:(CGRect)rect
{
NSLog(@"setNeedsDisplayInRect:%@", NSStringFromCGRect(rect));
[super setNeedsDisplayInRect:rect];
}
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)gc
{
CGRect bounds = CGContextGetClipBoundingBox(gc);
NSLog(@"drawLayer:inContext: bounds=%@", NSStringFromCGRect(bounds));
dispatch_async(dispatch_get_current_queue(), ^{
[self setNeedsDisplayInRect:bounds];
});
CGContextSetFillColorWithColor(gc, testColor.CGColor);
CGContextFillRect(gc, bounds);
sleep(0.2); // simulate the time it takes to draw complicated graphics
NSLog(@"end drawLayer:inContext: bounds=%@", NSStringFromCGRect(bounds));
}
As given, the code does cause drawing to repeat indefinitely, but sometimes there is a delay up to 5 seconds between the setNeedsDisplayInRect:, and the corresponding drawLayer:inContext:, in which nothing else is happening. See the log below as example. Note the irregular behaviour: in the first second, some tiles are redrawn multiple times, others only once. Then there is a pause of 5 seconds, and the cycle starts over again.
This was tested on the simulator with IOS6.0 (I choose that version, because earlier versions have another bug that is fixed in 6.0: they draw the same tiles twice sometimes).
2012-10-27 15:51:38.771 TiledLayerTest[39934:15a13] drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:38.774 TiledLayerTest[39934:15a13] end drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:38.774 TiledLayerTest[39934:1570f] drawLayer:inContext: bounds={{300, 0}, {20, 300}}
2012-10-27 15:51:38.776 TiledLayerTest[39934:1570f] end drawLayer:inContext: bounds={{300, 0}, {20, 300}}
2012-10-27 15:51:38.776 TiledLayerTest[39934:1630b] setNeedsDisplayInRect:{{0, 300}, {300, 180}}
2012-10-27 15:51:38.777 TiledLayerTest[39934:1540f] setNeedsDisplayInRect:{{300, 0}, {20, 300}}
2012-10-27 15:51:38.780 TiledLayerTest[39934:15a13] drawLayer:inContext: bounds={{300, 0}, {20, 300}}
2012-10-27 15:51:38.781 TiledLayerTest[39934:15a13] end drawLayer:inContext: bounds={{300, 0}, {20, 300}}
2012-10-27 15:51:38.782 TiledLayerTest[39934:1540f] setNeedsDisplayInRect:{{300, 0}, {20, 300}}
2012-10-27 15:51:38.789 TiledLayerTest[39934:1570f] drawLayer:inContext: bounds={{0, 0}, {300, 300}}
2012-10-27 15:51:38.791 TiledLayerTest[39934:15a13] drawLayer:inContext: bounds={{300, 300}, {20, 180}}
2012-10-27 15:51:38.792 TiledLayerTest[39934:15a13] end drawLayer:inContext: bounds={{300, 300}, {20, 180}}
2012-10-27 15:51:38.793 TiledLayerTest[39934:1570f] end drawLayer:inContext: bounds={{0, 0}, {300, 300}}
2012-10-27 15:51:38.795 TiledLayerTest[39934:1540f] setNeedsDisplayInRect:{{0, 0}, {300, 300}}
2012-10-27 15:51:38.795 TiledLayerTest[39934:1540f] setNeedsDisplayInRect:{{300, 300}, {20, 180}}
2012-10-27 15:51:38.798 TiledLayerTest[39934:15a13] drawLayer:inContext: bounds={{0, 0}, {300, 300}}
2012-10-27 15:51:38.800 TiledLayerTest[39934:15a13] end drawLayer:inContext: bounds={{0, 0}, {300, 300}}
2012-10-27 15:51:38.802 TiledLayerTest[39934:1630b] setNeedsDisplayInRect:{{0, 0}, {300, 300}}
2012-10-27 15:51:38.806 TiledLayerTest[39934:1570f] drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:38.808 TiledLayerTest[39934:1630b] setNeedsDisplayInRect:{{0, 300}, {300, 180}}
2012-10-27 15:51:38.809 TiledLayerTest[39934:1570f] end drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:38.813 TiledLayerTest[39934:15a13] drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:38.816 TiledLayerTest[39934:1630b] setNeedsDisplayInRect:{{0, 300}, {300, 180}}
2012-10-27 15:51:38.816 TiledLayerTest[39934:15a13] end drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:43.774 TiledLayerTest[39934:1540f] drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:43.776 TiledLayerTest[39934:1540f] end drawLayer:inContext: bounds={{0, 300}, {300, 180}}
2012-10-27 15:51:43.776 TiledLayerTest[39934:1630f] drawLayer:inContext: bounds={{0, 0}, {300, 300}}
回答1:
I've posted my answer to the similar issue: setNeedsDisplayInMapRect doesn't trigger new drawMapRect: call (just a link to not duplicate the answer here).
Shortly: you should dispatch a call of setNeedsDisplayInRect method to dispatch_get_main_queue().
回答2:
You can try using a NSRecursiveLock
in this way:
- (void) setNeedsDisplayInRect:(CGRect)rect
{
[self.lock lock]
NSLog(@"setNeedsDisplayInRect:%@", NSStringFromCGRect(rect));
[super setNeedsDisplayInRect:rect];
[self.lock unlock]
}
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)gc
{
[self.lock lock]
CGRect bounds = CGContextGetClipBoundingBox(gc);
NSLog(@"drawLayer:inContext: bounds=%@", NSStringFromCGRect(bounds));
// drawing code
NSLog(@"end drawLayer:inContext: bounds=%@", NSStringFromCGRect(bounds));
[self.lock unlock]
}
This ensures that setNeedsDisplayInRect
is not called while the drawing is in progress.
However this can affect the performance because the lock
can block the main thread and you cannot draw multiple tiles in parallel.
来源:https://stackoverflow.com/questions/8157349/setneedsdisplayinrect-called-during-drawlayerincontext