И как оптимизировать использование памяти в ваших циклах

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

  • Что такое пул авторелиза
  • Как это работает под капотом
  • Когда и как использовать его в кодовой базе Swift
  • Как это используется в Objective-C (полезно знать, поскольку этот вопрос иногда задают на технических собеседованиях)

Без дальнейших церемоний, давайте начнем.

Давайте начнем

Прежде чем мы определим пул автоматического освобождения, нам нужно знать, как работает подсчет ссылок. При создании ссылки выполняется команда retain, которая поддерживает объект в рабочем состоянии. Чтобы уменьшить счетчик ссылок и потенциально удалить объект из памяти, когда счетчик равен 0, запускается команда release. Автоматический подсчет ссылок (ARC) позаботится об этом за нас.

NSAutoreleasePool — это хранилище для объектов, которым отправляются release команды при опорожнении пула. Пул сохраняет эти объекты до тех пор, пока не будет вызван метод drain, что происходит, когда мы возвращаемся из контекста, создавшего этот пул. Например, когда у нас есть пул автоосвобождения в цикле for in, пул будет опорожняться после каждой итерации.

Теперь, когда мы знаем основы NSAutoreleasePool, давайте воспользуемся им в примере проекта.

Использование пула авторелиза

У нас есть простое приложение, которое показывает UIImageView в центре экрана:

Наша задача — загрузить 500 изображений и показать каждое изображение из этих UIImageView после загрузки изображения. Поэтому мы пишем следующий код внутри ViewController:

  1. Мы создаем последовательную DispatchQueue, которую будем использовать для задач загрузки изображений.
  2. Мы вызываем метод loadImages() внутри файла viewDidLoad().
  3. Получите URL-адрес, который извлекает данные случайного изображения за вызов.
  4. Запустите задачу загрузки изображения 500 раз
  5. После извлечения изображения покажите его в UIImageView.

Пока все вроде хорошо, изображения отображаются так, как ожидалось:

Однако давайте посмотрим, как это влияет на память с течением времени:

На изображении выше мы видим, что к тому времени, когда наше приложение проработало 60 секунд, мы использовали около 55 МБ памяти. Оказывается, каждое изображение сохранялось в памяти во время работы цикла. Мы могли бы оптимизировать процесс, отбрасывая предыдущее изображение после загрузки нового. И это именно то, чего мы собираемся достичь, используя autoreleasepool.

Давайте обновим код следующим образом:

Как мы видим, мы обернули задачу загрузки изображения в файл autoreleasepool. Давайте еще раз проверим объем памяти:

Здесь мы видим, что мы сократили использование памяти примерно на 70% после использования пула автоматического освобождения. Обратите внимание, что эта оптимизация работает не только с изображениями — мы также можем использовать пул автоматического освобождения при чтении файлов с диска.

Мы увидели, как может быть полезно использование пула авторелиза в Swift. Однако благодаря ARC в Swift нам больше не нужно использовать его слишком часто. Раньше в Objective-C использование пула авторелиза было довольно частым, особенно в случаях, когда мы хотим вернуть какой-то объект, который будет использоваться позже:

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

Теперь мы добавили команду освобождения после выделения памяти для экземпляра Obj. Однако теперь объект будет освобожден до возврата метода, что может привести к сбою при неправильной обработке.

В идеале мы хотим сохранить объект живым, пока внешний контекст содержит ссылку на объект, и уничтожить его, когда объект больше не используется. Вот тут-то и приходит на помощь трюк с автозапуском:

Ресурсы

Исходный код доступен на GitHub. Чтобы узнать больше о пуле авторелиза, посетите официальную документацию. Спасибо за прочтение!