Управление памятью в Swift 5
Было бы здорово, если бы мы, разработчики, имели дело с безграничной памятью и никогда не заботились о ее рациональном использовании. К сожалению, все далеко не так, и, следовательно, мы должны вести себя как арендаторы памяти на некоторое время, использовать ее, а затем возвращать ее обратно.
Swift - умный язык, и он знает, что многим разработчикам не нравится возвращать память обратно в среду; следовательно, он отслеживает выделенную память, используя, так называемый механизм ARC (автоматический подсчет ссылок).
В механизме ARC, если счетчик ссылок на ячейку памяти будет равен нулю, память повторно инициализируется и затем становится свободной для повторного использования.
Автоматический подсчет ссылок (ARC) и Очистка памяти (GC)
Многие языки программирования используют функцию очистки данных памяти для сбора неиспользуемой памяти, тогда как swift использует механизм ARC.
Технически, механизм ARC является формой очистки памяти и сбора неиспользуемых данных. Однако, говоря о сборе неиспользуемых данных, мы имеем в виду отдельный процесс, который выполняется независимо от приложения. Таким образом, освобождение памяти не может быть предсказано с помощью GC (Garbage Collector). Кроме того, при нехватке памяти сбор неиспользуемых данных может остановить выполнение потока операций, что требует намного большего количества ресурсов для бесперебойной работы.
Сбор неиспользуемых данных - это фоновый процесс, который выполняется в течение неопределенного времени жизненного цикла приложения (обычно во время простоя) в два этапа. На шаге 1 все объекты, которые считаются «безопасными для сбора», будут отмечены, а на шаге 2 эти объекты перемещаются и будут готовы для сбора.
ARC выполняет часть кода приложения, и, следовательно, он очень детерминированный. Объекты будут перемещены, как только они будут не нужны. Однако, в отличие от GC, ARC не может обнаружить эталонные циклы. Следовательно, ARC требует от разработчиков понимания взаимосвязи между эталонными объектами в системе и предотвращения циклов владения объектом/эталонных циклов.
Предотвращение выполнения циклов ссылок
Каждый объект имеет ссылку, а ARC никогда не обнуляется.
Главное, что нужно сделать разработчику - это предотвратить выполнения циклов владения объектов. Когда мы ссылаемся на объект, реализацией по умолчанию, которая приходит на ум, является сильна ссылка. Чтобы отслеживать циклы сохранения, нам нужно следить за отношениями родитель-потомок и следить за тем, чтобы дочерний элемент (потомок) не ссылался на родительский элемент « STRONGLY», а вместо этого помечался как не владеющий объектами элемент, т.е. слабый или не принадлежащий никаким элементам программы.
Циклы ссылок
В этом разделе мы увидим, как использовать слабые ссылки для разрыва циклов ссылок в графе объектов времени выполнения. Сначала мы создадим цикл сохранения в нашем коде, а затем разорвем этот цикл сохранения, используя слабые ссылки.
Имеется класс Employee, который выглядит следующим образом:
import Foundation
open class Employee {
public var name: String
public init(_ name: String) {
self.name = name
print("Employee \(name) initialised 🥳")
}
deinit {
print("Employee \(name) de-initialised 💀")
}
}
Видим, что при инициализации Employee отобразится сообщение Employee (employee name) initialised, а когда тот же сотрудник деинициализирован, отобразится имя Employee (name) de-initialised, так что можно наблюдать время жизни объектов Employee.
Позволим подклассу класса Employee создать класс Manager и класс Worker, как показано ниже:
public class Manager: Employee {
var reports: [Employee] = []
}
public class Worker: Employee {
var manager: Employee?
}
do {
let manager = Manager("Manager")
let employee1 = Worker("Employee 1")
employee1.manager = manager
let employee2 = Worker("Employee 2")
employee2.manager = manager
let employee3 = Worker("Employee 3")
employee3.manager = manager
manager.reports = [employee1, employee2, employee3]
}
Когда у нас будут оба этих класса, мы создадим один менеджер объектов Manager, три объекта Worker - employee1, employee2, employee3 - и затем назначаем соответствующий менеджер и свойства отчетов, как показано на фрагменте кода.
Оператор do создает область действия, выход из области действия будет выполнен после завершения работы оператора do.
Можете ли вы предсказать, что будет отображено после запуска плейграунда?
Цикл сохранения
Результаты выполнения выведут следующее на консоли Xcode:
Видим, что все четыре объекта инициализированы, как и ожидалось, но ни один из них не был деинициализирован, даже когда был выполнен выход из области. Почему это было?
Чтобы ответить на этот вопрос, давайте посмотрим на график распределения памяти для этих четырех объектов, которые были инициализированы в данной области:
Расположение объектов для кода
Как видим, manager строго относится к своим employees, и взамен каждый сотрудник - к manager. Следовательно, когда manager выходит из области, его счетчик ссылок равен 3, поэтому он не может быть деинициализирован. Аналогично, каждый employee имеет счетчик ссылок равен 1, поэтому ни один из них не будет деинициализирован, и получится утечка памяти.
«Слабые» на помощь
Мы можем решить проблему, пометив отношения менеджера в классах Worker как weak.
Weak всегда используется для переменной или свойства var и для optional.
Свойство, помеченное как weak, всегда является необязательным, поэтому, объект без хозяина удаляется, свойство optional автоматически сбрасывается на ноль. Поэтому все, что необходимо сделать, это безопасно обработать тип optional, и эта работа завершена :)
Давайте исправим код в примере выше.
public class Worker: Employee {
// Adding weak to this property will break the retain cycle
weak var manager: Employee?
}
Все, что необходимо будет выполнить, это добавить ключевое слово weak в опциональное свойство manager в классе Worker.
Поскольку слабая ссылка не удерживает сильную привязку к экземпляру, на который она ссылается, этот экземпляр может быть освобожден, пока слабая ссылка все еще ссылается на него.
Имейте в виду, что если вы установите какие-либо свойства наблюдателей на слабые свойства, они не будут вызываться, когда ARC устанавливает эту ссылку на ноль.
Замкнутый цикл ссылок нарушен
Результаты выполнения выведут следующее на консоли Xcode:
Все объекты, которые были инициализированы, теперь деинициализируются, как и ожидалось, как только они выйдут из области. Если ссылка на manager удаляется, ей больше ничего не принадлежит, и, следовательно, менеджер деинициализируется. И так как ссылка из отчетов исчезает, когда менеджер освобождается, все отчеты также исчезают. Теперь граф объектов выглядит следующим образом:
Пунктирная линия обозначает unowned ссылки для замыканий.
Оригинал статьи. Автор Navdeep Singh.
Перевел статью Дмитрий Ярмольчук.