4
votes

I ran into an interesting iOS problem today involving a CATiledLayer. This only happend on the device - not in the simulator.

My view draws in its CALayer via the drawLayer: inContext: delegate callback. This layer has a CATiledLayer-derived sublayer, which does its own drawing in an overridden drawInContext: method.

Both layers are rendering pdf content via CGContextDrawPDFPage(). (The CALayer draws a low res version, while the CATiledLayer sublayer draws hi-res content over the top.)

I ran into a scenario where I would be done with the view - would remove it from its superview and release it. dealloc() is called on the view. Sometime later, the CATiledLayer's drawInContext: method would be called (on a background thread), by the system. It would draw, but on return from the method Springboard would crash, and in doing so, bring down my app as well.

I fixed it by setting a flag in the CATiledLayer, telling it not to render anymore, from the view's dealloc method.

But I can only imagine there is a more elegant way. How come the CATiledLayer drawInContext: method was still called after the parent layer, and the parent-layer's view were deallocated? What is the correct way to shut down the view so this doesn't happen?

4

4 Answers

0
votes
-(void)drawLayer:(CALayer *)calayer inContext:(CGContextRef)context {    
   if(!self.superview)
      return;
   ...

UPDATE: As I recall, there were problems with this in older versions of iOS when it came to CATiledLayers, but setting the delegate to nil before dealloc is now the way to go. See: https://stackoverflow.com/a/4943231/2882

10
votes

The slow, but best way to fix is to also set view.layer.contents = nil. This waits for the threads to finish.

3
votes

Set view.layer.delegate to nil before releasing the view.

0
votes

Spent quite a long time on this. My latest approach is to declare a block variable and assign to self in viewWillDisappear method. Then invoke the setContents call onto a global dispatch queue - no need to lock up the main thread. Then when setContents returns invoke back on to the main thread and set the block variable to nil, which should ensure that the view controller is released on the main thread. One caveat though, I have found that it is prudent to use dispatch_after for the invoke onto the main thread as the global dispatch queue retains the view controller until it exits its block, which means you can have a race condition between it exiting (and releasing the view controller) and the main thread block setting the block variable to nil) which could lead to deallocation on the global dispatch queue thread.