iOS CATiledLayer 崩溃

发布于 2024-11-07 13:57:56 字数 352 浏览 5 评论 0原文

我有一个适用于 iPad 的 pdf 阅读器应用程序,我使用滚动视图来显示每个页面。我将页面保持在视图中,并将页面两侧的一页保持在视图中。我有单独的纵向和横向视图。纵向视图显示单页,横向查看器显示 2 页。

当 iPad 更改方向时,我会卸载旧方向的视图并加载新方向的视图。假设它处于纵向视图,然后更改为横向视图,应用程序会卸载纵向视图并加载横向视图。除非 pdf 文件很大,否则这一切都非常有效。

pdf 是使用tiledlayers 绘制的。当大型 pdf 文件的方向发生变化时,应用程序会崩溃。仅当在绘制所有图块之前更改方向时,应用程序才会崩溃。我的猜测是它正在崩溃,因为它试图将图块绘制到已卸载的视图上。那么有没有办法在我卸载视图时停止绘制图块呢?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

总攻大人 2024-11-14 13:57:56

您需要将 CALayer 的委托设置为 nil,然后将其从超级视图中删除。
这会停止渲染,之后您可以安全地解除分配。

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

另外,请确保从主线程调用它,否则可怕的错误将会等待着您。

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.

淡淡绿茶香 2024-11-14 13:57:56

我还没有查看反汇编,但我们使用的是稍微不同的解决方案。将 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(弱)id delegate 实现,但显然没有正确处理委托。看看一些反汇编,他们正在执行明显的种族绑定 if != nil 检查,然后直接触及弱属性。正确的方法是声明一个 __strong Type *delegate = self.delegate,它要么成功并为您提供一个保证生存的强引用,要么为 nil,但它当然不会给你一个僵尸对象的引用(我的猜测是框架代码还没有升级到ARC)。

在底层,CATiledLayer 创建一个调度队列来进行后台渲染,并且似乎要么以不安全的方式接触委托属性,要么获得本地引用但没有使其成为强引用。无论哪种方式,如果委托被释放,分派的渲染块都会愉快地向僵尸对象发送消息。仅仅清除委托是不够的——它会减少崩溃的数量,但不能安全地消除它们。

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

如果有人有改进建议,请告诉我。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文