iOS的CATiledLayer崩溃

我有一个iPad阅读器应用程序,我正在使用scrollview来显示每个页面。 我将页面保持在视图中,并且页面的任一侧都可以看到一页。 我有单独的肖像和风景的意见。 纵向视图显示单个页面,横向查看器显示2个页面。

当iPad改变方向时,我卸载旧方向的视图,并加载新方向的视图。 所以说这是在纵向视图,然后更改为横向应用程序卸载纵向视图,并加载横向视图。 这一切都很好,除非pdf很大。

pdf的使用tiledlayers绘制。 这个应用程序在大的pdf格式改变方向时会被刮掉。 如果在瓷砖全部被绘制之前改变方向,应用程序只会崩溃。 我的猜测是,它正在崩溃,因为它试图将瓷砖绘制到一个视图比已经被卸载。 那么当我卸载视图的时候有没有办法停止瓷砖的绘制?

您需要将CALayer的委托设置为零,然后将其从超级视图中移除。 这会停止渲染,之后可以安全地释放。

- (void)stopTiledRenderingAndRemoveFromSuperlayer; { ((CATiledLayer *)[self layer]).delegate = nil; [self removeFromSuperview]; [self.layer removeFromSuperlayer]; } 

另外,请确保从主线程中调用此函数,否则可怕的错误将等待您。

我没有看过反汇编看看,但我们正在使用一个稍微不同的解决scheme。 将CATiledLayer.content属性设置nil块,并强制所有排队的渲染块完成。 这可以安全地踢到后台线程,然后释放UIView可以踢回到主线程让视图和图层dealloc。

下面是一个UIViewController dealloc实现的例子,它将保持你的CATiledLayer拥有的视图足够长的时间来安全地停止渲染,而不会阻塞主线程。

 - (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; }); }); } } 

这是一个猜测 ,但苹果的一些框架/类(StoreKit,CATiledLayer,UIGestureRecognizer)声称拥有@property (weak) id delegate实现,但显然不能正确处理weak委托。 看一下反汇编, if != nil检查然后直接触及弱的属性,他们正在进行严格的比赛。 正确的方法是声明一个__strong Type *delegate = self.delegate ,这将成功,并给你一个强大的参考保证生存,或nil ,但它肯定不会给你一个僵尸对象的引用(我猜测是框架代码还没有升级到ARC)。

在引擎盖下, CATiledLayer创build一个调度队列来做后台渲染,并且看起来或者以一种不安全的方式触及委托属性,或者它获得一个本地引用,但没有强大的一个。 无论哪种方式,调度的渲染块将愉快地消息僵尸对象,如果委托取消分配。 只是清除代表是不够的 – 它会减less崩溃的数量,但不能安全地消除它们。

设置content = nil执行dispatch_wait并阻塞,直到所有现有的排队渲染块都完成。 我们跳回到主线程来确保dealloc是安全的。

如果有人有改进的build议,请让我知道。