Хотя ARC делает за вас большую часть работы с памятью, ваше приложение по прежнему может страдать от так называемых retain-циклов. Их обнаружение очень важно. Кстати по теме: у нас вы можете прочесть интересную статью о создании memory-эффективных приложений.
ARС и управление памятью
С введением автоматического подсчета ссылок (ARC) в iOS 5, работа с памятью значительно упростилась. Но ARC не может обрабатывать все сценарии. Так что обработка памяти наших приложений — по прежнему наша ответственность. Один из примеров retain-ссылки: каждый раз при переходе на ViewController, будет создаваться новый его объект, а старый не будет удаляться. Так их накопиться очень много. Если такое происходит очень часто — приложение будет насильно завершено операционной системой.
Retain-циклы
Давайте создадим пример retain-цикла. Во-первых, мы создадим RootViewController и SecondViewController. SecondViewController пудет показан, если кнопка на RootViewController будет нажата. Вы сможете создать такой переход легко с помощью Segue в Storyboard. Кроме того, у нас будет класс ModelObject, у которого будет объект делегата ModelObjectDelegate. Если SecondViewController загружен, то контроллер устанавливает себя как делегат для ModelObject:
1 2 3 4 5 6 7 8 9 10 11 | import Foundation protocol ModelObjectDelegate: class { } class ModelObject { var delegate: ModelObjectDelegate? } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import UIKit class SecondViewController: UIViewController, ModelObjectDelegate { var modelObject: ModelObject? override func viewDidLoad() { super.viewDidLoad() modelObject = ModelObject() modelObject!.delegate = self } @IBAction func closeButtonPressed(sender: UIButton) { dismissViewControllerAnimated(true, completion: nil) } } |
Теперь давайте рассмотрим обработку памяти. Если мы закрываем SecondViewController (closeButtonPressed), объем используемой памяти не уменьшится. Но почему это происходит? Мы ожидаем, что закрываясь, SecondViewController будет высвобожден из памяти. Если SecondViewController загружен, он выглядит таким образом:
Теперь, если SecondViewController будет закрыт, он будет выглядеть так:
RootViewController не имеет больше сильную (strong) ссылку на SecondViewController. Тем не менее, SecondViewController и ModelObject имеют сильные ссылки друг на друга, и из за этого они не освобождаются.
Хитрость
Хитрость, позволяющая обнаружить такого рода проблемы заключается в том, что если объект высвобождается, будет вызываться его метод deinit. Так что просто достаточно вставить какой то print в этот метод для отображения момента высвобождения в логе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import UIKit class SecondViewController: UIViewController, ModelObjectDelegate { var modelObject: ModelObject? override func viewDidLoad() { super.viewDidLoad() modelObject = ModelObject() modelObject!.delegate = self } @IBAction func closeButtonPressed(sender: UIButton) { dismissViewControllerAnimated(true, completion: nil) } deinit { print("SecondViewController deinit") } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import Foundation protocol ModelObjectDelegate: class { } class ModelObject { var delegate: ModelObjectDelegate? deinit { print("ModelObject deinit") } } |
Если мы закрываем SecondViewController, то видим, что в логе наше сообщение так и не появляется! Это означает что он не высвобожден, а значит что то не так.
Решение
Давайте изменим делегат на слабый (weak), что бы устранить одну мешающую нам сильную ссылку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import Foundation protocol ModelObjectDelegate: class { } class ModelObject { weak var delegate: ModelObjectDelegate? deinit { print("ModelObject deinit") } } |
Графически, наш объект выглядит теперь так:
Поскольку между SecondViewController и ModelObject теперь есть только одна сильная ссылка, мы ожидаем, что проблем больше не будет.
И в самом деле — теперь все сообщения методов deinit появляются в логе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import Foundation protocol ModelObjectDelegate: class { } class ModelObject { unowned var delegate: ModelObjectDelegate init(delegate:ModelObjectDelegate) { self.delegate = delegate } } |