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 崩溃的主要内容,如果未能解决你的问题,请参考以下文章

存档崩溃中的 iOS 崩溃似乎是块

iOS之CATiledLayer的属性简介和使用

Xcode查看iOS崩溃与崩溃日志分析

iOS应用崩溃日志分析 iOS应用崩溃日志揭秘

符号化没有崩溃日志的 iOS 崩溃堆栈跟踪

iOS测试常见崩溃