История вопроса:
С развитием Objective-C и появлением Swift в Apple встал вопрос автоматического и безопасного управления памятью приложений без явной работы программиста с командами retain/release. Swift использует Automatic Reference Counting (ARC), позволяющий отслеживать количество ссылок на объекты-классы и автоматически освобождать память, когда последний владелец исчезает.
Проблема:
Поскольку Swift является многопарадигменным языком, он оперирует и value-типами (struct, enum) и ссылочными типами (class). Для последних важно, чтобы память очищалась вовремя, иначе может возникнуть утечка памяти. Сложной ситуацией становится retain cycle (циклические ссылки), когда два объекта ссылаются друг на друга, не позволяя ARC освободить память.
Решение:
Swift применяет ARC только к объектам классов. Когда ссылок становится 0 — память высвобождается. Для решения проблемы retain cycle используются ключевые слова weak/unowned.
Пример кода:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Pet { var owner: Person? init() {} deinit { print("Pet is being deinitialized") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // Оба объекта НЕ освобождаются, возник retain cycle
Ключевые особенности:
Можно ли применять ARC к value-типам?
Нет, ARC отслеживает только ссылки на объекты классов. Структуры и enum'ы передаются по значению, и Swift освобождает их память автоматически (обычно при выходе из области видимости).
Что произойдет, если не использовать weak/unowned в свойствах двух взаимно ссылающихся объектов?
Произойдет циклическая ссылка (retain cycle), из-за чего ARC никогда не освободит память под эти объекты. Такой код приведет к memory leak — всегда используйте weak (или unowned) для как минимум одной стороны подобной связи.
Для чего нужен unowned, если есть weak?
unowned нужен в случаях, когда ссылка всегда должна существовать во время жизни другого объекта и никогда не должна становиться nil. weak — нулевая ссылка, unowned — не нулевая, но нет увеличения счётчика retain.
Пример кода:
class A { var b: B? } class B { unowned var a: A // никогда не nil во время жизни B }
Два контроллера содержат друг на друга сильные ссылки: один — через делегатное свойство, второй — через массив дочерних контроллеров. Создатель не использует weak у делегата.
Плюсы: Ошибки видны не сразу, все "работает" на первый взгляд.
Минусы: С ростом объёмов приложения память не освобождается, появляются утечки; при работе с ограниченным объёмом памяти возможны краши.
Делегат и наблюдатель событий настраиваются как weak, массив контроллеров чистится по мере исчезновения контроллеров. Всё аккуратно задокументировано.
Плюсы: Нет утечек, все объекты освобождаются своевременно, нет утрат производительности.
Минусы: Возможна утрата делегата неожиданно (если забыта сильная ссылка где-то выше); требует внимательности при архитектуре приложения.