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

Автоматический подсчет ссылок

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

  • Счетчик ссылок. Для управления владельцами объектов в ARC используется понятие, называемое счетчик ссылок. Заявление о праве собственности на объект увеличивает счетчик ссылок на этот объект на единицу. Как только счетчик ссылок упадет до нуля, объект будет освобожден.
  • Сильная ссылка утверждает право собственности на объект и увеличивает счетчик ссылок. Пока у кого-то есть сильная ссылка на объект, ARC не будет генерировать сообщение о выпуске для объекта, и он не будет освобожден. В Swift все ссылки являются сильными, если они не указаны.
  • Слабая сторона: если объект создан с использованием ключевого слова weak, у него нет владельца.

Если вам все еще не нравится концепция strong против weak, подумайте об объекте как о воздушном шаре (это лучшее объяснение, которое я когда-либо видел для этой темы, большое спасибо Мартину за это явное упрощение). Воздушный шар не улетит (отпущен), пока хотя бы один человек держит его за веревку. Количество людей, владеющих строками, является счетчиком удержания. У многих людей могут быть строки к одному и тому же воздушному шару. Ссылка strong похожа на то, как если бы вы держались за веревку на этом воздушном шаре. Пока вы держитесь за веревку, прикрепленную к воздушному шару, он не улетит. Ссылка weak похожа на воздушный шарик. Вы можете увидеть его, получить доступ к его свойствам, вызвать его методы, но у вас нет к нему строки. Если все, держащиеся за веревку, отпустят шарик, он улетит.

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

Сохранить цикл

Цикл сохранения или Цикл сильной ссылки происходит, когда два объекта имеют strong ссылок друг на друга. Их счетчики ссылок никогда не обнуляются, поэтому они никогда не освобождаются. Цикл сохранения происходит по разным сценариям. Вот список наиболее распространенных:

  1. Иерархические отношения
    Родительский класс всегда должен иметь strong ссылку на свои дочерние классы (это соглашение, а не правило, все, в чем мы должны быть уверены, так это в том, что мы не создаем двустороннюю связь). сильные ссылки, это должен быть либо родитель к дочернему, либо дочерний к родителю). Однако когда дочерний элемент хочет сослаться на своего родителя, он должен сделать его ссылкой weak, используя ключевое слово weak. Проще говоря, объект никогда не должен сохранять своего родителя или какого-либо иерархического предка.
  2. Взаимозависимые объекты
    Когда два объекта ссылаются друг на друга, следует использовать ссылку weak, чтобы избежать цикла сохранения. Вы можете найти известный пример Tenant-Apartment в Документации Swift.
  3. Замыкания
    Если какая-либо переменная объявлена за пределами области действия замыкания, ссылка на эту переменную внутри области действия замыкания создает еще одну сильную ссылку на этот объект (кроме Ints, Strings, Arrays и Dictionaries в Swift, поскольку это типы значений). Другими словами, класс сохраняет замыкание, которое сильно захватывает себя. Список захвата определяет, как ссылки на переменные фиксируются в замыкании. По умолчанию, если вы не используете список захвата, на все будут строго ссылаться. Чтобы избежать цикла сохранения, используйте ссылку weak или unowned на себя в списке захвата замыкания.
  4. Делегаты
    Использование Делегаты может вызвать цикл сохранения, если для делегата не задано значение weak, поскольку большинство делегатов ссылаются на классы, которыми они не владеют.

weak и unowned ключевые слова

Цель использования ключевого слова weak и unowned — избежать циклов сильных ссылок. Оба они не увеличивают счетчик ссылок на объект, на который ссылаются, и будут назначать экземпляр, не сохраняя сильную ссылку на него.

weak используется, когда вы знаете, что ссылка может стать nil, поэтому она должна быть определена как необязательная переменная. Они будут изменены на nil после освобождения.

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

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

Какой из них использовать?
Когда два экземпляра могут быть связаны друг с другом, weak является правильным вариантом.
Бывают случаи, когда два экземпляра класса связаны друг с другом, но один одного из этих экземпляров не может существовать без другого (например, код города для номера телефона). Они взаимозависимы, поэтому они всегда будут освобождаться одновременно. В этом случае следует использовать unowned.

Существует важная проблема с использованием unowned. Вы можете думать о unowned как о свойстве weak, которое неявно развертывается (хотя unowned имеет небольшое улучшение производительности за счет полного игнорирования подсчета ссылок). Поэтому, если вы используете unowned, вы должны быть осторожны в случаях, когда переменная становится nil.

Ссылочные типы и типы значений

Struct и Enum являются типами значений, что означает, что при создании нового экземпляра их значения копируются вместо увеличения счетчика ссылок, поэтому они не создают сильных циклов ссылок. Это означает, что если вы имеете дело с типами значений, вам даже не нужно беспокоиться об указании для них strong или weak. Чтобы узнать больше о различиях между типами значений и ссылочными типами, проверьте этот пост.

Дальнейшее обучение

Автоматический подсчет ссылок
Вопросы интервью: как осуществляется управление памятью в iOS?
Сохранение циклов, слабых и бесхозных в Swift
«Слабый, сильный, бесхозный, о боже !” — Руководство по ссылкам в Swift
Сильные, слабые и неизвестные — Разбираем ARC и Swift
Список захвата