Обнаружение retain-циклов

О

Хотя ARC делает за вас большую часть работы с памятью, ваше приложение по прежнему может страдать от так называемых retain-циклов. Их обнаружение очень важно. Кстати по теме: у нас вы можете прочесть интересную статью о создании memory-эффективных приложений.

ARС и управление памятью

С введением автоматического подсчета ссылок (ARC) в iOS 5, работа с памятью значительно упростилась. Но ARC не может обрабатывать все сценарии. Так что обработка памяти наших приложений — по прежнему наша ответственность. Один из примеров retain-ссылки: каждый раз при переходе на ViewController, будет создаваться новый его объект, а старый не будет удаляться. Так их накопиться очень много. Если такое происходит очень часто — приложение будет насильно завершено операционной системой.

Retain-циклы

Давайте создадим пример retain-цикла. Во-первых, мы создадим RootViewController и SecondViewController. SecondViewController пудет показан, если кнопка на RootViewController будет нажата. Вы сможете создать такой переход легко с помощью Segue в Storyboard. Кроме того, у нас будет класс ModelObject, у которого будет объект делегата ModelObjectDelegate. Если SecondViewController загружен, то контроллер устанавливает себя как делегат для ModelObject:

Теперь давайте рассмотрим обработку памяти. Если мы закрываем SecondViewController (closeButtonPressed), объем используемой памяти не уменьшится. Но почему это происходит? Мы ожидаем, что закрываясь, SecondViewController будет высвобожден из памяти. Если SecondViewController загружен, он выглядит таким образом:

retain

Теперь, если SecondViewController будет закрыт, он будет выглядеть так:

retain

RootViewController не имеет больше сильную (strong) ссылку на SecondViewController. Тем не менее, SecondViewController и ModelObject имеют сильные ссылки друг на друга, и из за этого они не освобождаются.

Хитрость

Хитрость, позволяющая обнаружить такого рода проблемы заключается в том, что если объект высвобождается, будет вызываться его метод deinit. Так что просто достаточно вставить какой то print в этот метод для отображения момента высвобождения в логе:

Если мы закрываем SecondViewController, то видим, что в логе наше сообщение так и не появляется! Это означает что он не высвобожден, а значит что то не так.

Решение

Давайте изменим делегат на слабый (weak), что бы устранить одну мешающую нам сильную ссылку:

Графически, наш объект выглядит теперь так:

retain

Поскольку между SecondViewController и ModelObject теперь есть только одна сильная ссылка, мы ожидаем, что проблем больше не будет.

И в самом деле — теперь все сообщения методов deinit появляются в логе:

SecondViewController deinit
ModelObject deinit
Это то поведение, которое мы ожидали!
Вместо weak вы так же можете использовать ключевое слово unowned. Но в чем разница? Если вы используете weak, то свойство необязательно, а значит имеет право быть nil. При использовании unowned свойство не должно быть опциональным. Так как unowned свойство не является опциональным, его значение должно быть установлено в методе инициализации:
В зависимости от того, опционально свойство или нет, вы можете использовать как weak так и unowned.
Хотя это и не много, но вы должны вставить логирование в метод deinit объектов для того, что бы всегда видеть, что происходит с ними. Вы также можете использовать инструменты Xcode для выявления retain-циклов. Но разве это проще, чем вставить одну строку в метод deinit?

 

Поддержите ресурс blog.justDev:

Сведения об авторе

Игорь Малеваный

Добавить комментарий

Instagram

Поддержите ресурс blog.justDev:

Свежие записи

Рубрики