I have a pdf reader app for the iPad where I am using a scrollview to display each page. I keep the page in view and one page either side of the page in view. I have seperate views for portrait and landscape views. The portrait view showns a single page and the landscape viewer shows 2 pages.
When the iPad changes orientation I unload the view for the old orientation and load the view for the new orientation. So say it was in portrait view and then changes to landscape the app unloads the portrait view and loads the landscape view. This all works great except when the pdf's are large.
The pdf's are drawn using tiledlayers. The app is scrashing when the orientation is changed with large pdf's. The app only crashes if the orientation is changed before the tiles have all been drawn. My guess is that it is crashing because it is trying to draw tiles to a view than has been unloaded. So is there a way to stop the drawing of tiles when I unload the view?
You need to set CALayer's delegate to nil, then remove it from the superview. This stops rendering, afterwards you can safely dealloc.
- (void)stopTiledRenderingAndRemoveFromSuperlayer; {
((CATiledLayer *)[self layer]).delegate = nil;
[self removeFromSuperview];
[self.layer removeFromSuperlayer];
}
Also, make sure to call this from the main thread, or else horrible bugs will await you.
I haven't looked at the disassembly to see, but we are using a slightly different solution. Setting the CATiledLayer.content
property to nil
blocks and forces all queued render blocks to complete. That can safely be kicked off to a background thread, then releasing the UIView
can be kicked back to the main thread to let the view and layer dealloc.
Here's one example of a UIViewController dealloc
implementation that will keep your CATiledLayer
owning view alive long enough to safely stop rendering, without blocking the main thread.
- (void)dealloc
{
// This works around a bug where the CATiledLayer background drawing
// delegate may still have dispatched blocks awaiting rendering after
// the view hierarchy is dead, causing a message to a zombie object.
// We'll hold on to tiledView, flush the dispatch queue,
// then let go of fastViewer.
MyTiledView *tiledView = self.tiledView;
if(tiledView) {
dispatch_background(^{
// This blocks while CATiledLayer flushes out its queued render blocks.
tiledView.layer.contents = nil;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Make sure tiledView survives until now.
tiledView.layer.delegate = nil;
});
});
}
}
This is a guess, but some of Apple's frameworks/classes (StoreKit, CATiledLayer, UIGestureRecognizer) claim to have @property (weak) id delegate
implementations but clearly do not properly handle the weak
delegate. Looking at some disassembly and they are doing decidedly race-bound if != nil
checks then touching the weak property directly. The proper way is to declare a __strong Type *delegate = self.delegate
, which will either succeed and give you a strong reference guaranteed to survive, or be nil
, but it certainly won't give you a reference to a zombie object (my guess is that framework code has not been upgraded to ARC).
Under the hood, CATiledLayer
creates a dispatch queue to do the background rendering and appears to either touch the delegate property in an unsafe way or it obtains a local reference but without making it a strong one. Either way, the dispatched render blocks will happily message a zombie object if the delegate gets deallocated. Just clearing the delegate is insufficient - it will reduce the number of crashes but does not safely eliminate them.
Setting the content = nil
does a dispatch_wait and blocks until all the existing queued render blocks are finished. We bounce back to the main thread to make sure the dealloc
is safe.
If anyone has suggestions for improvement please let me know.
来源:https://stackoverflow.com/questions/6012796/ios-catiledlayer-crash