Привет!
В этой лекции мы рассмотрим управление памятью.
Как Swift решает, что объект можно удалить и как мы можем повлиять на эту логику.
В Swift нет сборщика мусора, но управление памятью происходит автоматически.
Для этого используется Automatic Reference Counting, или сокращенно ARC.
Эта система пришла в Swift из Objective C.
Давайте рассмотрим, как она работает.
Идея проста: когда вы создает объект ARC создает счетчик указателей на него.
Каждый раз, когда вы присваиваете этот объект какой-нибудь переменной,
константе или свойству, этот счетчик увеличивается.
При удалении указателя — уменьшается.
Если он достигнет нуля, то такой объект считается ненужным и удаляется из памяти.
Рассмотрим пример.
В нем мы объявим простой класс с инициализатором и деинициализатором,
чтобы можно было понять, был ли объект удален.
В комментариях указано количество ссылок на объект.
Когда reference1 присваивается reference2,
то новый объект не создается — это reference type.
Вместо этого ARC просто увеличивает количество ссылок.
Когда обнуляются обе ссылки, экземпляр будет удален.
Рассмотрим пример посложнее.
Объявлены классы для хранения пользователя и устройства.
У пользователя есть массив: его устройство и имя, у устройства — модель и владелец.
Для таких целей лучше использовать структуры,
но в данном примере используются классы, чтобы не усложнять код описанием ситуации,
где действительно нужны классы.
Создадим пользователя — Боба, добавим ему iPhone 7 и попробуем,
как раньше, обнулить ссылки.
Запустив этот пример у себя в плейграунде, вы убедитесь,
что объекты не были удалены после обнуления ссылок.
Произошло это потому,
что количество ссылок пользователя и его девайс не равны 0.
У объекта Боб есть ссылка на iPhone 7, у айфона — ссылка на его владельца,
то есть они ссылаются друг на друга, и никогда не будут удалены.
Такая ситуация называется strong reference cycle.
Не забывайте, что возникнуть она может не только с парой объектов,
ссылающихся друг на друга — их может быть любое количество.
Для того чтобы решить эту проблему, существуют слабые ссылки.
В отличие от обычных, сильных, ссылок они не учитываются ARC.
Если на объект ссылаются слабые ссылки, но нет ни одной сильной,
то объект все равно будет удален.
При этом все weak-ссылки будут обнулены.
Сделаем owner слабой ссылкой.
Обратите внимание на то, что owner теперь объявлена как переменная, а не константа,
и тип ее — optional.
Это логично, ведь слабая ссылка будет обнуляться при удалении объекта,
на который она ссылается.
Теперь все объекты будут корректно удаляться.
Запустив, исправленную версию в плейграунде, можно в этом убедиться.
Помимо weak, есть еще ключевое слово unowned.
Разница между ними в том, что unowned-ссылки
не обнуляются при удалении объекта, на который они указывают.
Так как Swift — безопасный язык, то при обращении к такой переменной,
он не позволит повредить данные и завершит выполнение программы.
Это поведение можно изменить при помощи ключевого слова unowned unsafe.
В этом случае мы получим undefined behavior,
то есть поведение приложения нельзя будет предсказать.
Но у unowned есть одно преимущество: не обязательно использовать
optional — это позволяет упростить код.
В общем случае следует руководствоваться следующим правилом:
если время жизни ссылки такое же, как и у объекта, на который она указывает,
то можно использовать unowned.
Но не только классы являются ссылочными типами в Swift — при использовании
замыканий тоже может возникнуть strong reference cycle.
Рассмотрим пример.
Есть класс counter, у него свойствосодержащее closure expression.
В этом замыкании есть обращение к self.
Это приводит к тому,
что замыкание захватит self и возникнет strong reference cycle.
Для решения этой проблемы тоже используется weak и unowned.
Указываются они в списке захвата, перед названием константы.
В данном случае использовался unowned,
потому что замыкание не может существовать без объекта Counter.
Однако не следует добавлять такой захват в каждое замыкание.
Если в нем нет обращения к объекту, в котором оно хранится,
то и проблем с памятью не возникнет.
Также обратите внимание на то, является ли замыкание escaping.
Если нет, то и strong refernce cycle возникнуть не может.
Все вышесказанное относилось только к reference type.
У значимых типов нет посчета ссылок — при присваивании новой переменной
создается копия, поэтому у них всегда один владелец.
При его удалении просто удалятся все объекты, которыми он владеет.
На этом лекция по управлению памятью закончена.
Мы разобрались, как работает счетчик ссылок,
что такое strong reference cycle и какие существуют способы его избежать.