我有一个适用于iPad的pdf阅读器应用程序,我使用滚动视图来显示每个页面。我将页面保持在视图中,并在页面的任一侧查看一页。我有纵向和横向视图的独立视图。纵向视图显示单个页面,横向查看器显示2页。
当iPad改变方向时,我会卸载旧方向的视图并加载新方向的视图。所以说它是在纵向视图中然后更改为横向应用程序卸载纵向视图并加载横向视图。这一切都很有效,除非pdf很大。
pdf是使用tileslayers绘制的。当方向改变为大pdf时,应用程序正在进行清理。如果在绘制完所有图块之前更改了方向,则应用程序仅会崩溃。我的猜测是它崩溃了,因为它试图将瓷砖绘制到视图而不是已卸载。那么当我卸载视图时有没有办法停止绘制图块?
答案 0 :(得分:4)
您需要将CALayer的委托设置为nil,然后将其从superview中删除。 这会停止渲染,之后您可以安全地解除分类。
- (void)stopTiledRenderingAndRemoveFromSuperlayer; {
((CATiledLayer *)[self layer]).delegate = nil;
[self removeFromSuperview];
[self.layer removeFromSuperlayer];
}
此外,请务必从主线程中调用此方法,否则可能会出现可疑的错误。
答案 1 :(得分:3)
我没有看过反汇编,但我们使用的是一个稍微不同的解决方案。将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;
});
});
}
}
这是一个猜测,但Apple的一些框架/类(StoreKit,CATiledLayer,UIGestureRecognizer)声称有@property (weak) id delegate
个实现,但显然无法正确处理weak
代表。看一些反汇编,他们正在进行明确的种族限制if != nil
检查,然后直接触摸弱房产。正确的方法是声明一个__strong Type *delegate = self.delegate
,它会成功并为你提供一个强大的参考保证能够存活,或者是nil
,但当然不会给你对僵尸对象的引用(我的猜测是框架代码尚未升级到ARC)。
在幕后,CATiledLayer
创建一个调度队列来进行后台渲染,并且看起来要么以不安全的方式触摸委托属性,要么获得本地引用但不使其成为强引用。无论哪种方式,如果委托被取消分配,调度的渲染块将很乐意向僵尸对象发送消息。只是清除代表是不够的 - 它会减少崩溃次数,但不能安全地消除它们。
设置content = nil
会执行dispatch_wait并阻塞,直到所有现有的排队渲染块都完成为止。我们回到主线程以确保dealloc
是安全的。
如果有人有改进建议,请告诉我。