iOS应用扩展的最大内存预算

12
根据苹果的App Extension编程指南: 在运行应用扩展时,内存限制显著低于对前台应用施加的内存限制。在两个平台上,系统可能会因为用户想返回主应用程序中的主要目标而积极终止扩展。一些扩展的内存限制可能比其他扩展更低:例如,小部件必须特别高效,因为用户可能同时打开多个小部件。
App Extension有非常严格的内存限制,并且每种类型的App Extension都不同。
每种类型的App Extension的最大内存预算是多少?可以像测试iOS App一样进行测试吗?参考链接:ios app maximum memory budget

1
应用程序扩展没有固定的内存预算。扩展可以使用的内存量与其他使用内存的进程相关联。例如,我曾经有过小部件在使用约4000个rpages(16mb)时被终止,而其他时候它们在使用1000个rpages时被终止。这些小部件被终止的主要原因不是因为它们使用了太多内存,而是因为它们无法在操作系统要求释放内存时释放内存。 - Sander Saelmans
我有一个 iMessage 应用程序扩展,在 iPhone X 上使用约 100 MB 时被终止,系统显示有 200 MB+ 的空闲空间。至少我认为这就是它退出的原因。 - drewster
3个回答

5
我在iPad Pro 9.7上遇到了一次崩溃,提示信息如下:
Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)

在2GB的iOS设备上,它占用了120 MB的存储空间,但请注意,限制取决于扩展的类型
示例:
苹果确认网络扩展的内存限制为5-6MB,自iOS 10 Beta 2以来提高到15MB。这些限制对于某些应用程序是不足的(请参见Psiphon3 ticket)。
测试VPN(网络扩展NEPacketTunnelProvider)会产生以下错误:
Thread 1: EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=15 MB, unused=0x0)

1
我的今日扩展在第六代iPad上在26 MB时崩溃了。 - Emma Labbé
@ColdGrub1384 遇到了同样的问题。在我的代码中,它将图像资源分配给Storyboard中的UIImageView。这会增加Storyboard二进制文件的大小。我已经将其删除并在代码中分配了图像。问题得到解决。 - toxicsun
当我尝试给一个抓取的图像添加叠加层时,遇到了同样的问题。对于超过8-10mb的任何内容都会崩溃。但是,如果不将其分配给UIImageView而是创建一个带有图形上下文的UIImage,则在drawInRect处崩溃。vImage_ERROR_Buffer_Write__Too_Small_For_Arguments_To_vImage__CheckBacktrace: - user1139479
当我们关闭扩展时,如何释放内存?在我的情况下,每当我打开扩展时,内存都会不断增加。当我第五次打开它时,它会崩溃并显示超过内存限制的错误信息。 - Anita Nagori
你的扩展程序可能存在内存泄漏问题,请检查自己的代码。 - Owen Zhao

1
在我的情况下,我试图在UIImageView中显示一张5500 x 3000像素的图片,但在iPhone SE上崩溃(EXC_RESOURCE RESOURCE_TYPE_MEMORY (limit=120 MB, unused=0x0)),但在模拟器上没有崩溃。
运行内存分析可以帮助找出问题。在分配给UIImageView.image之前缩小图像大小可以解决问题。

因为我也在扩展中遇到了问题。始终记住 RAM 限制不会在模拟器上强制执行。 - voidStern

0

我试图通过扩展从共享图像创建缩略图,并发现当通过NSItemProvider提供的URL加载UIImage,然后通过以下方式创建缩略图:

UIGraphicsImageRenderer(bounds: thumbnailBounds).image { context in
   image.draw(in: thumbnailBounds)
}

对于较大的图像,会导致扩展内存溢出(>120mb)。我的解决方案是在从URL直接创建UIImage之前将图像降采样到适合我的应用程序的像素大小,以便通过共享扩展管理而不会导致内存溢出。

private func resizeForUpload(_ imageURL: URL) -> UIImage? {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    let maxDimensionInPixels: CGFloat = 2000
    let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
                                     kCGImageSourceShouldCacheImmediately: true,
                               kCGImageSourceCreateThumbnailWithTransform: true,
                                      kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
    
    guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions), let downsampledImage =  CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
        return nil
    }
    
    return UIImage(cgImage: downsampledImage)
}

这是使用 private func resizeForUpload(_ imageURL: URL) -> UIImage? 的方法。

if itemProvider.hasItemConformingToTypeIdentifier(SystemUTType.image) {
        itemProvider.loadItem(forTypeIdentifier: SystemUTType.image, options: nil) { secureCoding, error in
            if let url = secureCoding as? URL,
               let image = self.resizeForUpload(url) {
                // The iOS Photos app comes here.
                completion(image, nil)
            } else if let image = secureCoding as? UIImage {
                // The iOS screenshot editor comes here.
                // The iOS Notes app comes here.
                completion(image, nil)
            } else if let data = secureCoding as? Data,
                       let image = UIImage(data: data) {
                completion(image, nil)
            } else {
                completion(nil, error)
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接