iOS CATiledLayer 崩溃
Posted
技术标签:
【中文标题】iOS CATiledLayer 崩溃【英文标题】:iOS CATiledLayer crash 【发布时间】:2011-08-26 03:34:27 【问题描述】:我有一个适用于 iPad 的 pdf 阅读器应用程序,我在其中使用滚动视图来显示每个页面。我将页面保持在视图中,并在页面的任一侧保持一个页面。我对纵向和横向视图有单独的视图。纵向视图显示单页,横向查看器显示 2 页。
当 iPad 改变方向时,我会卸载旧方向的视图并加载新方向的视图。所以说它是纵向视图,然后更改为横向应用程序卸载纵向视图并加载横向视图。这一切都很好,除非 pdf 很大。
pdf 是使用 tiledlayers 绘制的。当使用大 pdf 更改方向时,应用程序正在崩溃。仅当在所有图块绘制完成之前更改方向时,应用程序才会崩溃。我的猜测是它正在崩溃,因为它试图将图块绘制到视图中而不是已卸载。那么有没有办法在我卸载视图时停止绘制瓷砖?
【问题讨论】:
我没有找到合适的解决方案,但解决方法可以防止崩溃。最终将 if (self.tiling == YES && [self.view superview] != nil) 放入 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 方法。在从超级视图中删除视图之前或在您的情况下,在方向更改之前设置自平铺。您可以通过为所有方向设置一个视图来避免此问题。我的解决方法防止了崩溃,但并没有阻止 CATiledLayer 以某种神秘的方式访问图像或锁定图像。最终结果是无法从文件系统中删除所述图像。祝你好运! 【参考方案1】:您需要将 CALayer 的委托设置为 nil,然后将其从超级视图中删除。 这会停止渲染,之后您可以安全地解除分配。
- (void)stopTiledRenderingAndRemoveFromSuperlayer;
((CATiledLayer *)[self layer]).delegate = nil;
[self removeFromSuperview];
[self.layer removeFromSuperlayer];
另外,请确保从主线程中调用它,否则可怕的错误将等待您。
【讨论】:
1) 你确定[self.layer removeFromSuperlayer];
是必需的吗? 2)您的保留/释放有助于防止崩溃这一事实是否表明您仍有可能发生的竞争条件?保留可以防止 dealloc
被调用(作为在另一个线程上释放的结果),但是在函数开始到调用保留之间仍可能发生 dealloc,并且您最终将在已释放的内存上调用保留。
我还发现你还必须在设置self.layer.delegate nil之前删除所有子视图,否则它们不会被释放。
你是对的,不需要保留/释放 - 我更新了我的答案。
先生,你很漂亮(更不用说PSPDFKit
的美丽了=P)。【参考方案2】:
我没有查看反汇编来查看,但我们使用的是稍微不同的解决方案。将CATiledLayer.content
属性设置为nil
块并强制所有排队的渲染块完成。这可以安全地被踢到后台线程,然后释放UIView
可以被踢回主线程以让视图和层释放。
这是一个 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
是安全的。
如果有人有改进建议,请告诉我。
【讨论】:
以上是关于iOS CATiledLayer 崩溃的主要内容,如果未能解决你的问题,请参考以下文章