我如何知道哪些对象专门消耗了我的 iOS 应用程序中的所有内存/RAM?

Dou*_*ith 5 cocoa-touch memory-management instruments uiimage ios

假设我有一个应用程序,我注意到它的内存使用率很高。我如何确定什么占用了特定对象的所有内存。我可以通过 Xcode 内存调试器以某种方式做到这一点吗?仪器?

以这段代码为例:

class RootViewController: UIViewController {
    var image: UIImage?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let data = try! Data(contentsOf: URL(string: "https://effigis.com/wp-content/uploads/2015/02/Airbus_Pleiades_50cm_8bit_RGB_Yogyakarta.jpg")!)
        self.image = UIImage(data: data)
    }
}
Run Code Online (Sandbox Code Playgroud)

该 URL 处的图像大约为 40 MB,在本例中,它对我的​​应用程序的大量内存占用产生了很大影响。

我如何确定“哦,是的,就是这个UIImage本身占用了 40 MB 内存!”

Rob*_*Rob 7

简短回答:

\n

不幸的是,对于这个给定的大内存分配,\xe2\x80\x99s 没有简单的\xe2\x80\x9c,它与这个特定的UIImage\xe2\x80\x9d 相关联。您可以在 Instruments\xe2\x80\x99 \xe2\x80\x9cAllocations\xe2\x80\x9d 工具或 Xcode \xe2\x80\x9cDebug 内存图\xe2\x80\x9d 中使用堆栈跟踪(使用 \xe2\ x80\x9cmalloc stack\xe2\x80\x9d 功能),以识别在哪里分配了什么,但使用它来跟踪来自一些大型 malloc 的图像数据和原始对象是非常困难的UIImage。对于简单的对象,它工作得很好,但对于图像来说,它\xe2\x80\x99s 有点复杂。

\n

长答案:

\n

图像的挑战在于,为图像数据分配的内存通常在某种程度上与UIImage对象本身分离。对象的分配UIImage很容易追溯到实例化它的位置,但不能追溯到支持图像的数据的缓冲区。更糟糕的是,当我们将此图像提供给某个图像视图时,该图像缓冲区的堆栈跟踪会将您带入渲染引擎调用树,而不是您的代码,这使得它变得更加困难。

\n

话虽如此,使用 Instruments,您通常可以获得有关\xe2\x80\x99s 正在发生的情况的线索。例如,使用 \xe2\x80\x9cAllocations\xe2\x80\x9d 工具,转到分配列表,查看分配的内容。如果您获取该列表,按大小对其进行排序,您可以在右侧看到分配位置的堆栈跟踪:

\n

在此输入图像描述

\n

现在在这种情况下,我在 a 中使用了图像UIImageView,因此生成的分配被隐藏在 iOS 框架内,而不是直接到我们的代码中。但我们可以从堆栈跟踪中推断出这是在 UI 中渲染此 JPG 的结果。

\n

因此,虽然您可以\xe2\x80\x99t 轻松得出\xe2\x80\x9coh 的结论,\xe2\x80\x99s 是特定的Airbus Pleiades 图像,\xe2\x80\x9d 您至少可以断定特定分配是相关的以及一些JPG。

\n
\n

一些不相​​关的观察结果:

\n
    \n
  1. 我怀疑你只是让你的例子简单,但显然你永远不会使用Data(contentsOf:)像那样从主线程使用。您的 UI 将被阻止,您的应用程序将面临被看门狗进程杀死的风险。

    \n

    您通常会异步发起网络请求:

    \n
     let url = URL(string: "https://effigis.com/wp-content/uploads/2015/02/Airbus_Pleiades_50cm_8bit_RGB_Yogyakarta.jpg")!\n\n URLSession.shared.dataTask(with: url) { data, _, _ in\n     guard\n         let data = data,\n         let image = UIImage(data: data)\n     else {\n         return\n     }\n\n     DispatchQueue.main.async {\n         self.image = image\n     }\n }.resume()\n
    Run Code Online (Sandbox Code Playgroud)\n

    这不仅可以避免阻塞主线程,而且如果您想要对给定错误进行任何特殊处理(例如,UI 中的自定义错误消息或其他内容),理论上您可以使用URLResponse和参数。Error

    \n
  2. \n
  3. 下载这样的大型资源时,如果您不需要立即在 UI 中显示图像,您可以使用下载任务,它的峰值内存使用量比Data(contentsOf:)或 a低得多dataTask

    \n
     let url = URL(string: "https://effigis.com/wp-content/uploads/2015/02/Airbus_Pleiades_50cm_8bit_RGB_Yogyakarta.jpg")!\n let filename = url.lastPathComponent\n\n URLSession.shared.downloadTask(with: url) { location, _, _ in\n     guard let location = location else { return }\n\n     do {\n         let folder = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)\n             .appendingPathComponent("images")\n         try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)\n         let fileURL = folder.appendingPathComponent(filename)\n         try FileManager.default.moveItem(at: location, to: fileURL)\n     } catch {\n         print(error)\n     }\n }.resume()\n
    Run Code Online (Sandbox Code Playgroud)\n

    如果您这样做,在下载过程中您将不需要任何接近 40mb 的内容。如果下载大量资源或者您\xe2\x80\x99 没有立即在 UI 中显示图像,这可能很重要。另外,如果您稍后选择使用后台URLSession,则可以对下载任务执行此操作,但不能对数据任务执行此操作。

    \n
  4. \n
  5. 值得注意的是,JPG 图像(以及较小程度的 PNG 图像)通常是经过压缩的。因此,您可以轻松地发现您正在下载的资产的大小可能以千字节为单位,但当您使用它时,将需要兆字节。一般的经验法则是,无论您使用的文件大小或使用它的控件的大小,使用图像时所需的内存通常为 4 \xc3\x97宽度 \xc3\x97 高度(以像素为单位测量)。

    \n

    例如,当您使用 5,494 \xc3\x97 5,839 像素的图像时,它可能会占用 122 mb (!)。细节可能有所不同,但 4 \xc3\x97 width \xc3\x97 height 是一个很好的假设。在考虑内存消耗时,文件的大小会误导您在使用此资源时可能使用的内存量。始终考虑实际图像尺寸,因为在使用时它\xe2\x80\x99s 将被解压缩。

    \n
  6. \n
  7. 在上面的回答中,我重点关注 Instruments\xe2\x80\x99 分配工具。但值得注意的是,在诊断内存使用情况时,当您尝试诊断强引用在哪里时,\xe2\x80\x9cDebug Memory Graph\xe2\x80\x9d 功能非常有用(非常适合识别强引用循环)。它与此特定讨论并不真正相关,但如果您要跟踪使用图像的位置,它可能会很有用。

    \n

    例如,在这里,我\xe2\x80\x99已经下载了你的图像(使用URLSession),不仅设置了image我的视图控制器的属性,而且还在UIImageView. 这个 \xe2\x80\x9cDebug Memory Graph\xe2\x80\x9d 工具非常适合可视化在何处使用什么(但不可否认,不是用于将特定内存分配与代码相关联):

    \n

    在此输入图像描述

    \n

    我还编辑了我的方案\xe2\x80\x99s诊断选项以包含\xe2\x80\x9cmalloc堆栈\xe2\x80\x9d功能,在右侧为我提供堆栈跟踪,就像您在上面的分配工具中看到的那样。

    \n
  8. \n
\n

  • 特殊的答案,点赞是不够的 (2认同)

Mar*_*zyk 1

Instruments 中的 Allocations 工具可以做到这一点。从跳转栏中选择分配列表将显示您的应用程序进行的每个内存分配。按分配大小对表进行排序以查看最大的内存分配。

大多数开发人员感兴趣的是找到分配大量内存的代码。我在以下链接回答了这个问题:

使用仪器工具定位泄漏

我知道问题的标题是关于泄漏的,但该技术对于内存分配来说是相同的。