Как узнать, какие объекты конкретно используют всю память / оперативную память в моем приложении iOS? - PullRequest
0 голосов
/ 09 июля 2020

Допустим, у меня есть приложение, и я заметил, что оно сильно загружает память. Как определить, ЧТО занимает всю память с точки зрения указанного c объекта (ов). Могу ли я как-нибудь сделать это через отладчик памяти 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)
    }
}

Размер изображения по этому URL-адресу составляет около 40 МБ, и в этом примере он значительно увеличивает объем памяти моего приложения.

Как определить «О да, это UIImage вот тут занимает 40 МБ памяти!»

Ответы [ 2 ]

0 голосов
/ 10 июля 2020

Краткий ответ:

К сожалению, не существует простого «для данного большого выделения памяти, оно связано с этим конкретным UIImage». Вы можете использовать трассировку стека либо в инструменте «Распределение» инструментов, либо в Xcode «График памяти отладки» (с функцией «mallo c stack»), чтобы определить, что было выделено где, но это чрезвычайно сложно использовать для отслеживания из какого-то большого mallo c для данных изображения и исходного объекта UIImage. Для простых объектов он работает нормально, но для изображений он немного более запутан. из самого объекта UIImage. Размещение объекта UIImage легко отследить до того места, где вы его создали, но не до буфера для данных, поддерживающих изображение. Хуже того, когда мы предоставляем это изображение какому-либо представлению изображения, трассировка стека для этого буфера изображения приведет вас к дереву вызовов движка рендеринга, а не к вашему коду, что еще больше усложнит задачу.

* 1012 , вы часто можете понять, что происходит. Например, с помощью инструмента «Распределения» введите go в список распределений и посмотрите, что и где было выделено. Если вы возьмете этот список, отсортируйте его по размеру, и вы увидите трассировку стека справа, где он был размещен:

enter image description here

Now in this case, I used the image in a UIImageView, and therefore the resulting allocation is buried inside the the iOS frameworks, not directly to our code. But one can infer from the stack trace that this was the result of rendering this JPG in the UI.

So, while you can’t easily conclude “oh, that’s the specific Airbus Pleiades image,” you can at least conclude that the particular allocation was associated with some JPG.


A few unrelated observations:

  1. I suspect you were just keeping your example simple, but obviously you would never use Data(contentsOf:) from the main thread like that. Your UI will be blocked and you risk having your app killed by the watchdog process.

    You'd generally initiate the network request asynchronously:

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

    This not only avoids blocking the main thread, but you theoretically could use the URLResponse and Error parameters if you wanted any special handling for given errors (e.g. customized error messages in the UI or whatever).

  2. When downloading large assets like this, if you don’t need to show the image in the UI immediately, you might use a download task instead, which has a much lower peak memory usage than Data(contentsOf:) or a dataTask:

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

    If you do this, you won't require anything close to the 40mb during the download process. That might be critical if downloading lots of assets or if you’re not immediately showing the image in the UI. Also, if you later choose to use background URLSession, you can do this with download tasks, but not data tasks.

  3. It’s worth noting that JPG images (and to a lesser degree, PNG images) are generally compressed. Thus, you can easily find that you might be downloading an asset whose size may be measured in kilobytes, but when you go to use it, will require megabytes. The general rule of thumb is that, regardless of the size of the file you use or the size of the control in which you’re using it, the memory required when you use the image is generally 4 × width × height (measured in pixels).

    For example, a 5,494 × 5,839 px image may take up 122 mb (!) when you go to use it. The particulars may vary, but 4 × width × height is a good assumption. When considering memory consumption, the size of the file is a misleading indication of the amount of memory that might be used when you go to use this asset. Always consider the actual image dimensions because it’s going to be uncompressed when you use it.

  4. In my answer above, I focused on Instruments’ Allocations tool. But it's worth noting that when diagnosing memory usage, the “Debug Memory Graph” feature is great when you’re trying to diagnose where the strong references are (great for identifying strong reference cycles). It’s not really relevant to this particular discussion, but can be useful if you’re tracking down where you used an image.

    For example, here, I’ve downloaded your image (using URLSession) and not only set the image property of my view controller, but also used it in a UIImageView. This “Debug Memory Graph” tool is great for visualizing what is used where (but admittedly, not for correlating specific memory allocations to code):

    введите описание изображения здесь

    Я также редактирую параметры диагностики c моей схемы, чтобы включить функцию «mallo c stack», давая мне трассировку стека справа, как вы видите в инструменте Allocations выше .

0 голосов
/ 10 июля 2020

Инструмент распределения в инструментах может это сделать. Выбор списка распределения на панели перехода покажет каждое выделение памяти, выделенное вашим приложением. Отсортируйте таблицу по размеру выделения, чтобы увидеть самые большие выделения памяти.

Большинство разработчиков интересует поиск кода, который выделяет большие объемы памяти. Я ответил на этот вопрос по следующей ссылке:

Использование инструментов для поиска утечек

Я знаю, что заголовок вопроса касается утечек, но методика работает так же для выделения памяти.

...