为什么 UIImageView 比 CGContextDrawImage 更占用内存

发布于 2024-10-16 12:19:22 字数 577 浏览 1 评论 0原文

在开发 iPad PDF 阅读器时,我们决定准备渲染密集型页面(其中有很多路径)的高分辨率图像,并使用这些图像而不是 pdf 页面来避免性能问题。我们认为 3*768 x 3*1024 是可读性和渲染性能之间的良好折衷方案,可生成 ~1.5 MB jpeg

然而,我们测试了两种显示图像页面的实现。一个使用 CATiledLayer 子类,该子类还负责处理“普通”PDF 页面(使用 CGContextDrawImage 绘制),另一个使用 UIImageView。后者的优点是显示和缩放非常快,但内存使用率非常糟糕 - 大约需要 30 MB 内存(这与图像的位图大小一致)。另一种方法 (CATiledLayer) 需要更多时间来首次显示页面,并且在缩放后需要另外两秒钟重新渲染(类似于 pdf 页面,但快得多),但不会占用比它需要显示更小的图像或 PDF 页面。

有谁知道幕后发生了什么,以及是否可以通过使用 Quartz 框架将 CGContextDrawImage 的低内存使用率与 UIImageView 的高性能结合起来。

Developing an iPad PDF-Reader we decided to prepare high-res images of rendering intensive pages (lots of paths in them) and use those instead of the pdf pages to avoid performance issues. We decided that 3*768 by 3*1024 is a good compromise between readability and rendering performance which results in ~1.5 MB jpegs.

However we tested two implementations for displaying the image pages. One that uses a CATiledLayer subclass which is also responsible for handling the "normal" PDF pages (drawing with CGContextDrawImage) and another which uses UIImageView. The latter has the advantage that displaying and zooming is very quick, but memory usages is really bad - it takes about 30 MB in memory (which is consistent with the image's bitmap size). The other approach (CATiledLayer) needs more time to first display the page and needs another two seconds to re-render after zooming (similar to pdf pages, but much faster) but doesn't grab more memory than it needs to display a much smaller image or a PDF page.

Does anyone know what's going on behind the scenes and if it's possible to combine low memory usage of CGContextDrawImage with high performance of UIImageView by using the Quartz Framework.

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

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

发布评论

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

评论(1

妳是的陽光 2024-10-23 12:19:22

不确定这个问题是否仍然相关,但如果是这样,也许这会有所帮助:

我已经在我的 平铺图像视图也是如此。这个问题有很多潜在的部分:

  • 一个正常的 UIView,包括 UIImageView,似乎总是完全由内存支持其图像。即使您实现了drawRect:方法,它似乎总是传递视图的整个边界,而不仅仅是滚动视图中的可见区域。正如您所发现的,这很快就会占用大量内存,因为每个像素占用 4 个字节。
  • CATiledLayer 确实仅请求可见图块的内容。它还会丢弃不再可见的图块 - 这就是节省内存的来源。但是,它在后台线程上执行通知和绘图,同时从白色到内容进行动画处理。它似乎是通过私有 API 来做到这一点的,我还没有找到一种方法来重新实现 CATiledLayer 的功能作为我自己的 CALayer 子类,因为看起来很简单没有任何通知机制可供我们普通人使用。
  • 如果滚动视图中有多个视图,它们会在分页时适当地接收 drawRect: 消息。不过,UIKit 似乎很难处理视图下面太多的子视图。

对于您来说,我可以看到几个可能的选择:

  • 可能可以挽救基于 CATiledLayer 的实现。 fadeDuration 默认为 0.25 秒,如果您的加载时间很短,该时间可能会太长。您可以将其直接降低为 1.0/60.0 之类的值,即一帧。您的描述中不清楚的一件事是您的图像是覆盖整个页面大小还是仅覆盖每个 256x256 像素图块。对每个图块一遍又一遍地解码整个 JPEG 将比解码单个图块文件慢得多。
  • 如果从 CATiledLayer 线程执行所有操作的延迟太高,您可以手动创建一堆图块作为空白 UIViewUIImageView 子视图这是滚动视图的主要子视图。每个子视图都分配有自己的平铺图像。如果幸运的话,UIKit 将足够智能,删除这些视图的内容并根据需要重新加载相应的 JPEG。
  • 如果 UIImageViews 不够智能,或者你有太多 UIKit 无法处理的视图,你可以构建自己的视图,在 drawRect: 中加载它们的 JPEG。如果它仍然太不稳定,请尝试使用您自己的 CALayers,它将成为单个视图空白层的子层,并再次根据需要加载其图像。这就是我最终选择的平铺图像视图。

这应该涵盖滚动,但如果您允许用户直接缩小(缩小),它仍然可能非常慢。在这种情况下,我建议您存储适当的较低分辨率版本并加载/显示最外层缩放级别的版本。

Not sure if this question is still relevant, but if so, maybe this will help:

I've fought the battle of efficient display of large views in my tiled image view as well. There are a bunch of underlying parts to the problem:

  • A normal UIView, including UIImageView, seems to always be completely backed by memory for its image. Even if you implement the drawRect: method, it always seems to pass the entire bounds of the view, not only the area visible within a scroll view. As you've discovered, this quickly takes up a lot of memory as each pixel takes 4 bytes.
  • The CATiledLayer does only request the contents for visible tiles. It also discards tiles that are no longer visible - this is where the memory savings come from. However, it does the notification and the drawing on a background thread, while animating from white to the contents. It seems to do this via a private API, I haven't found a way to re-implement CATiledLayer's functionality as my own subclass of CALayer, as there simply seems to be no notification mechanism that we can use as mere mortals.
  • If you have multiple views within the scroll view, they receive drawRect: messages appropriately as they are paged in. UIKit seems to struggle with too many subviews below a view, though.

For you, I can see a couple of possible options:

  • It might be possible to salvage the CATiledLayer-based implementation. The fadeDuration defaults to 0.25 seconds, which may be too long if your load times are short. You can drop this right down to something like 1.0/60.0, i.e. one frame. One thing that's not clear from your description is whether your images cover the whole page size or just each 256x256 pixel tile. Decoding the whole JPEG over and over again for each tile will be much slower than decoding individual tile files.
  • If the latency from doing everything from the CATiledLayer thread is too high, you can manually create a bunch of tiles as UIImageView subviews to a blank UIView which is the main subview to the scroll view. Each of these subviews is assigned its own tile image. If you're lucky, UIKit will be smart enough to drop the contents of these views and re-load the corresponding JPEGs on demand.
  • If UIImageViews aren't smart enough, or you have too many views for UIKit to cope, you can build your own views which load their JPEGs in drawRect:. If it's still too jerky, try with your own CALayers, which will be sublayers of your single view's blank layer and again load their image on demand. This is what I eventually went with for my tiled image view.

That should cover scrolling, but it could still be really slow if you allow the user to zoom right out (minification). In that case I recommend you store appropriate lower-resolution versions and load/show those at those outermost zoom levels.

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