retain cycle swift что это

Управление памятью в Swift

В этой статье я, по традиции, попробую собрать основную информацию по управлению памятью в iOS (Преимущественно в swift). По этой теме куча клевой инфы, поэтому материал вышел объемный из переводов (мб где-то кривых) и заимствований. Но для объяснений на пальцах основных моментов он получился нормальным

Структура

Управление памятью в современных языках программирования

Value Refence type

Weak, Strong, Unowned

Swift Object Lifecycle

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

Управление памятью в современных языках программирования

Управление памятью — является важной даже в современных технологиях. Неправильное использование может привести к долгой загрузке, крашам приложения или даже операционной системы.

Для чего используется оперативная память?

Когда программа выполняется в операционный системе компьютера, она нуждается в доступе к оперативной памяти (RAM) для того, чтобы:

загружать свой собственный байт-код для выполнения;

хранить значения переменных и структуры данных, которые используются в процессе работы;

загружать внешние модули, которые необходимы программе для выполнения задач.

Помимо места, используемого для загрузки своего собственного байт-кода, программа использует при работе две области в оперативной памяти — стек (stack) и кучу (heap).

Stack — Переменные, выделенные в стеке, хранятся непосредственно в памяти, и доступ к этой памяти очень быстрый, и ее выделение определяется при компиляции программы.

Стек используется для статичного выделения памяти. Он организован по принципу «последним пришёл — первым вышел» (LIFO). Можно представить стек как стопку книг — разрешено взаимодействовать только с самой верхней книгой: прочитать её или положить на неё новую.

Стек позволяет очень быстро выполнять операции с данными — все манипуляции производятся с «верхней книгой в стопке». Книга добавляется в самый верх, если нужно сохранить данные, либо берётся сверху, если данные требуется прочитать;

Существует ограничение в том, что данные, которые предполагается хранить в стеке, обязаны быть конечными и статичными — их размер должен быть известен ещё на этапе компиляции;

Каждый поток многопоточного приложения имеет доступ к своему собственному стеку;

Когда функция вызывается, все локальные экземпляры этой функции будут помещены в текущий стек. И как только функция вернется, все экземпляры будут удалены из стека.

Если размер вашего value type может быть определен во время компиляции или если ваш value type не содержит рекурсию на себя или не находится в ссылочном типе, тогда потребуется выделение стека.

Куча используется для динамического выделения памяти, однако, в отличие от стека, данные в куче первым делом требуется найти с помощью «оглавления». Можно представить, что куча это такая большая многоуровневая библиотека, в которой, следуя определённым инструкциям, можно найти необходимую книгу.

Операции на куче производятся несколько медленнее, чем на стеке, так как требуют дополнительного этапа для поиска данных;

В куче хранятся данные динамических размеров, например, список, в который можно добавлять произвольное количество элементов;

Куча общая для всех потоков приложения;

Вследствие динамической природы, куча нетривиальна в управлении и с ней возникает большинство всех проблем и ошибок, связанных с памятью. Способы решения этих проблем предоставляются языками программирования;

Подходы в управлением памяти

Ручное управление памятью

Сборщик мусора на основе алгоритма пометок

Сборщик мусора с подсчётом ссылок

Автоматический подсчёт ссылок (ARC)

Ручное управление памятью

Язык не предоставляет механизмов для автоматического управления памятью. Выделение и освобождение памяти для создаваемых объектов остаётся полностью на совести разработчика. Пример такого языка — C. Он предоставляет ряд методов (malloc, realloc, calloc и free) для управления памятью — разработчик должен использовать их для выделения и освобождения памяти в своей программе. Этот подход требует большой аккуратности и внимательности. Так же он является в особенности сложным для новичков.

Сборщик мусора

Сборка мусора — это процесс автоматического управления памятью в куче, который заключается в поиске неиспользующихся участков памяти, которые ранее были заняты под нужды программы. Это один из наиболее популярных вариантов механизма для управления памятью в современных языках программирования. Подпрограмма сборки мусора обычно запускается в заранее определённые интервалы времени и бывает, что её запуск совпадает с ресурсозатратными процессами, в результате чего происходит задержка в работе приложения. JVM (Java/Scala/Groovy/Kotlin), JavaScript, Python, C#, Golang, OCaml и Ruby — вот примеры популярных языков, в которых используется сборщик мусора.

Сборщик мусора на основе алгоритма пометок (Mark & Sweep)
Это алгоритм, работа которого происходит в две фазы: первым делом он помечает объекты в памяти, на которые имеются ссылки, а затем освобождает память от объектов, которые пометки не получили. Этот подход используется, например, в JVM, C#, Ruby, JavaScript и Golang. В JVM существует на выбор несколько разных алгоритмов сборки мусора, а JavaScript-движки, такие как V8, используют алгоритм пометок в дополнение к подсчёту ссылок. Такой сборщик мусора можно подключить в C и C++ в виде внешней библиотеки.

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

    Сборщик мусора с подсчётом ссылок
    Для каждого объекта в куче ведётся счётчик ссылок на него — если счётчик достигает нуля, то память высвобождается. Данный алгоритм в чистом виде не способен корректно обрабатывать циклические ссылки объекта на самого себя. Сборщик мусора с подсчётом ссылок, вместе с дополнительными ухищрениями для выявления и обработки циклических ссылок, используется, например, в PHP, Perl и Python. Этот алгоритм сборки мусора так же может быть использован и в C++

    Автоматический подсчёт ссылок (ARC)

    Данный подход весьма похож на сборку мусора с подсчётом ссылок, однако, вместо запуска процесса подсчёта в определённые интервалы времени, инструкции выделения и освобождения памяти вставляются на этапе компиляции прямо в байт-код. Когда же счётчик ссылок достигает нуля, память освобождается как часть нормального потока выполнения программы.

    Автоматический подсчёт ссылок всё так же не позволяет обрабатывать циклические ссылки и требует от разработчика использования специальных ключевых слов для дополнительной обработки таких ситуаций. ARC является одной из особенностей транслятора Clang, поэтому присутствует в языках Objective-C и Swift.

    Концепция Ownership

    Что такое это ваше право собственности?

    В любом языке с концепцией разрушения есть понятие собственности. В некоторых языках, таких как C и Objective-C, не относящиеся к ARC, владение явно контролируется программистами. В других языках, таких как C ++ (частично), владение управляется языком. Даже в языках с неявным управлением памятью все еще есть библиотеки с концепциями владения, потому что помимо памяти есть и другие программные ресурсы, и важно понимать, какой код отвечает за высвобождение этих ресурсов.

    У Swift уже есть система владения, но она «под прикрытием»: это деталь реализации, на которую программисты не могут повлиять

    О ней можно долго говорить, а лучше ознакомиться здесь, но вкратце основные идеи этой концепции:

    Каждое значение в памяти должно иметь только одну переменную-владельца

    Когда владелец уходит из области выполнения, память сразу же освобождается. (Можно сказать, что это примерно как подсчёт ссылок на этапе компиляции).

    Каждая часть кода несет ответственность за то, чтобы в конечном итоге вызвать уничтожение объекта

    Окей, вроде с основными моментами разобрались. Давайте теперь перейдем к Swift. Начнем с базовых вещей

    Value/Reference Types

    Value и Reference Types — это основные концепции Swift. В Swift есть три способа объявления типа: классы, структуры и перечисления. Их можно разделить на типы значений (структуры и перечисления) и ссылочные типы (классы). То, как они хранятся в памяти, определяет разницу между ними:

    Value Type — каждая переменная типа значения имеет свою собственную копию данных, и операции с одной не влияют на другую. За него отвечает стэк.

    Reference Type — у нас есть ссылка, указывающая на это место в памяти. Переменные ссылочного типа могут указывать на одни и те же данные; следовательно, операции с одной переменной могут повлиять на данные, указанные другой переменной. За него отвечает куча.

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

Основные замеры по которым делается сравнение структур и классов это:

стоимость allocation и deallocation

стоимость подсчета ссылок

Что такое память?

Адресное пространство приложения iOS логически состоит из четырех сегментов: текста, данных, стека и кучи.

Текстовый сегмент содержит машинные инструкции, которые образуют исполняемый код приложения. Он создается компилятором путем перевода кода Swift в машинный код. Этот сегмент доступен только для чтения и занимает постоянное место.

Сегмент данных хранит статические переменные Swift, константы и метаданные типов. Сюда попадают все глобальные данные, которым требуется начальное значение при запуске программы.

В стеке хранятся временные данные: параметры метода и локальные переменные. Каждый раз, когда мы вызываем метод, в стеке выделяется новая часть памяти. Эта память освобождается при выходе из метода. За некоторыми исключениями сюда попадают все типы значений Swift.

В куче хранятся объекты, у которых есть время жизни. Это все ссылочные типы Swift и некоторые случаи типов значений. Куча и стопка растут навстречу друг другу.

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

Затраты на выделение и освобождение памяти в куче намного больше, чем на выделение памяти в стэке (https://developer.apple.com/videos/play/wwdc2018/416/)

Хотя типы значений и ссылки обычно выделяются в стеке и куче соответственно, есть исключения из этих правил.

Назначение в Stack ссылочных типов

Компилятор Swift может продвигать ссылочные типы для размещения в стеке, когда их размер фиксирован или время жизни может быть предсказано. Эта оптимизация происходит на этапе генерации SIL.

Компилятор Swift может упаковывать типы значений и размещать их в куче:

При соблюдении протокола. Помимо затрат на выделение ресурсов, возникают дополнительные накладные расходы, когда тип значения хранится в экзистенциальном контейнере и превышает длину 3 машинных слов.

При смешивании value и reference типов. Обычно ссылка на класс хранится в структуре, а структура является полем класса:

3. Generic с value типом

4.Escaping closure captures.

В сlosure все локальные переменные фиксируются по ссылке. Некоторые из них все еще могут быть переведены в стек, как описано в CapturePromotion.

SIL указывает, что определение функции закрывается над символической ячейкой памяти. Эта инструкция является переменной.

5.Inout аргумент

Аргументы @inout передаются в точку входа по адресу. Вызываемый объект не получает права собственности на указанную память. Указанная память должна быть инициализирована при входе в функцию и выходе из нее. Если аргумент @inout относится к хрупкой физической переменной (Unowned Unsafe), то аргументом является адрес этой переменной. Если аргумент @inout относится к логическому свойству, тогда аргумент является адресом буфера обратной записи, принадлежащего вызывающей стороне.

Цена копирования

Как говорили выше, большинство value types размещаются в стеке, и их копирование занимает постоянное время. На скорость влияет то, что примитивные типы, такие как целые числа и числа с плавающей запятой, хранятся в регистрах ЦП, и при их копировании нет необходимости обращаться к оперативной памяти. Большинство расширяемых типов Swift, таких как строки, массивы, наборы и словари, копируются при записи (copy-on-write). Это означает, что копирование происходит только в момент мутации.

Поскольку ссылочные типы не хранят свои данные напрямую, мы несем затраты на подсчет ссылок только при их копировании.

Все становится интересно, когда мы смешиваем значения и ссылочные типы. Если структуры или перечисления содержат ссылки, они будут платить накладные расходы на подсчет ссылок пропорционально количеству ссылок, которые они содержат

В iOS есть 2 вида подсчета ссылок:

Manual Reference Counter

Automatic reference counter (ARC)

Давайте разберем каждый по отдельности

Manual Reference Counter

По сути, вы выделяете объект, сохраняете его в какой-то момент, а затем отправляете один release для каждого отправленного вами alloc/retain. Метод dealloc вызывается для объекта, когда он удаляется из памяти.

Но у такого подхода есть минусы, о которых мы говорили вначале:

Нужно постоянно считать retain, release

Крэш при обращении из выгруженного из памяти

Automatic Reference Counter

У property появились модификаторы:

Но есть и минусы, с которыми не справляется ARC. О них мы тоже говорили выше и iOS эти проблемы никуда не ушли:

Deep Dive ARC

Swift Runtime представляет каждый динамически выделяемый объект со структурой HeapObject. Он содержит все части данных, которые составляют объект в Swift: количество ссылок и метаданные типов.

Внутри каждый объект Swift имеет три счетчика ссылок: по одному для каждого типа ссылки. На этапе генерации SIL компилятор swiftc вставляет вызовы методов swift_retain () и swift_release (), где это необходимо. Это делается путем перехвата инициализации и уничтожения HeapObjects.

Типы ссылок

Retain cycles (о нем говорили выше)

Не всегда возможно сделать сильные ссылки действительными сразу при создании объекта, например с делегатами.

Weak решают проблему обратных ссылок. Объект может быть уничтожен, если на него указывают слабые ссылки. Слабая ссылка возвращает nil, когда объект, на который она указывает, больше не жив. Это называется обнулением.

Unowned ссылки имеют различную разновидность weak, рассчитанные на инварианты жесткой валидности. Необнуляемые ссылки не обнуляются. При попытке прочитать несуществующий объект по неизвестной ссылке программа выйдет из строя с ошибкой. Четкой причины их юзать до сих пор не знают и много спорят, но все выводы уходят в легкость дебагинга.

Side Tables

Side tables — это механизм для реализации слабых ссылок Swift.

Обычно объекты не имеют слабых ссылок, поэтому резервировать место для подсчета слабых ссылок в каждом объекте нецелесообразно. Эта информация хранится извне в дополнительных таблицах, поэтому ее можно выделить только тогда, когда это действительно необходимо.

Как только мы начинаем ссылаться на объект слабо (weak reference), то создается боковая таблица, и теперь объект вместо сильного счетчика ссылок хранит ссылку на боковую таблицу. Сама боковая таблица также имеет ссылку на объект.

Side Table — это просто счетчик ссылок + указатель на объект. Они объявлены в Swift Runtime следующим образом (код C ++)

Жизненный цикл объекта Swift

На Live состоянии объект жив. Его счетчики ссылок выставлены по 1. Если есть указатель на слабую ссылку, то создается side table

Когда strong RC достигает нуля, вызывается deinit(), и объект переходит в следующее состояние. Это состояние Deiniting. На данном этапе операции со strong ссылками не действуют. При чтении через unowned ссылку будет срабатывать assertion failure. Но новые unowned ссылки еще могут добавляться. Если есть боковая таблица, то weak операции будут возвращать nil. Далее из этого состояния уже можно перейти в два других.

Первое: если нет боковой таблицы (то есть нет weak ссылок) и нет unowned ссылок, то объект переходит в Dead состояние и сразу удаляется из памяти.

Второе: если у нас есть unowned или weak ссылки, объект переходит в состояние Deinited. В этом состоянии функция deinit() завершена. Сохранение и чтение сильных или слабых ссылок невозможно. Как и сохранение новых unowned ссылок. При попытке чтения unowned ссылки вызывается assertion failure. Из этого состояния также возможно два исхода.

В том случае, если нет слабых ссылок, объект переходит непосредственно в состояние Dead, которое было описано выше.

В случае наличия weak ссылок, а значит и боковой таблицы, осуществляется переход в состояние Freed (Освобожден). В Freed состоянии объект уже полностью освобожден и не занимает места в памяти, но его боковая таблица остается жива.

После того как счетчик слабых ссылок достигает нуля, боковая таблица также удаляется и освобождает память, и осуществляется переход в финальное состояние — Dead.

В мертвом состоянии от объекта ничего не осталось, кроме указателя на него. Указатель на HeapObject освобождается из кучи, не оставляя следов объекта в памяти.

Autoreleasepool

В эпоху Obj-C в iOS использование этого типа было важным для предотвращения утечек памяти вашего приложения в определенных случаях

Что такое @autoreleasepool?

В дни ручного управления памятью, до ARC для Obj-C, для управления потоком памяти в приложении iOS приходилось использовать функции keep () и release (). Поскольку управление памятью iOS работает на основе счетчика сохраненных объектов, пользователи могут использовать эти методы, чтобы сигнализировать, сколько раз на объект ссылаются, чтобы его можно было безопасно отключить, если это значение когда-либо достигнет нуля.

Вместо того, чтобы мгновенно уменьшить счетчик удержания объекта, autorelease () добавляет объект в пул объектов, которые необходимо освободить когда-нибудь в будущем, но не сейчас. По умолчанию пул освобождает эти объекты в конце RunLoop’a выполняемого потока, чего более чем достаточно, чтобы покрыть все случаи. Или почти все

Нужен ли @autoreleasepool в Swift ARC?

Ответ в зависимости от обстоятельств. Если наш проект содержит obj-c код, то да. В чистом swift проекте возможно это потребуется только в каких-то библиотеках, которые содержат obj-c код.

Summary

Слабые ссылки указывают на Поинт в side table. Unowned и Strong ссылки указывают на объект.

Автоматический подсчет ссылок реализован на уровне компилятора. Компилятор swiftc вставляет вызовы для освобождения и сохранения там, где это необходимо

Объекты Swift не уничтожаются сразу. Вместо этого они проходят 5 этапов своего жизненного цикла: live → deiniting → deinited → freed → dead.

Источник

You don’t (always) need [weak self]

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

Cycles… no, not the fun kind shown above. I mean strong reference cycles, the kind that causes entire view controllers to leak in your iOS app. More specifically, I want to talk about the use of [weak self] inside of Swift closures to avoid reference (or retain) cycles, and explore cases where it may or may not be necessary to capture self weakly.

I learned about the topics discussed in this article from reading Apple docs, a variety of blog posts and tutorials, and through trial & error and experimentation. If you think I made a mistake somewhere, feel free to reach out in the comments or on Twitter.

I also put together a small app that demonstrates different memory leak scenarios and also shows where using [weak self] may be unnecessary:

almaleh/weak-self

Automatic Reference Counting

Memory management in Swift is handled by ARC (Automatic Reference Counting), which works behind the scenes to free up the memory used by class instances once they’re no longer needed. ARC works mostly by itself, but sometimes you need to provide it with some extra information to clarify the relationships between your objects.

For example, if you have a child controller that stores a reference to its owner/parent in a property, that property would need to be marked with the weak keyword to prevent a circular reference / retain cycle.

If you suspect there might be a memory leak, you can:

As for closures, let’s consider this code:

Notice how self was captured weakly in that closure, which subsequently turned it into an optional in the body of the closure.

Do we really need [weak self] here? If we don’t use it, would that introduce a memory leak? 🤔

The answer, as it turns out, is “it depends”, but first let me share some history.

Unowned, Weak, and the Strong-Weak Dance

Closures can strongly capture, or close over, any constants or variables from the context in which they are defined. For example, if you use self inside a closure, the closure scope will maintain a strong reference to self for the duration of the scope’s life.

If self also happens to keep a reference to this closure (in order to call it at some point in the future), you will end up with a strong reference cycle.

Luckily, there are tools such as the keywords unowned and weak (as well as other tools discussed below) that can be used to avoid this circular reference.

When I first learned Swift, I used [unowned self] in all my closures. Later on (and after several crashes 😅), I discovered that this is the equivalent to force unwrapping self and trying to access its contents even after it gets deallocated. In other words, it’s very unsafe!

[weak self] accomplishes the same task (preventing reference cycles) in a much safer manner, but it also turns self into an optional in the process. To deal with this optionality, you can prefix your calls with self?. optional chaining. However, a more popular approach is to create a temporary strong reference to self at the start of the closure by using guard let syntax.

In earlier iterations of the Swift language, it was common to perform what was known as the Strong-Weak dance, where you would assign self to a temporary non-optional strongSelf constant like this:

Then, later on, people started using (or abusing 😛 ) a compiler bug with backticks to simplify the code further:

Eventually, with Swift 4.2, the language added official support for guard let self = self syntax, so this became possible:

Erica Sadun endorses the guard let self = self pattern in her book Swift Style, Second Edition, so I’d say it’s pretty safe to use it 😃

It may be tempting to use unowned over weak to avoid dealing with optionality, but generally speaking, only use unowned when you are certain that the reference will never be nil during the execution of the closure. Again, it’s like force unwrapping an optional, and if happens to be nil, you will crash. [weak self] is a far safer alternative.

Here’s what a crash caused by unowned looks like:

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

This was pretty much me for a while:

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

But as it turns out, I was introducing optionality in many places in my code where it wasn’t really needed. And the reason comes down to the nature of the closures I was dealing with.

Escaping vs non-escaping closures

There are two kinds of closures, non-escaping and escaping. Non-escaping closures are executed in scope — they execute their code immediately, and cannot be stored or run later. Escaping closures, on the other hand, can be stored, they can be passed around to other closures, and they can be executed at some point in the future.

Non-escaping closures (such as higher-order functions like compactMap) do not pose a risk of introducing strong reference cycles, and thus do not require the use of weak or unowned

I made this flowchart to help illustrate the concept:

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

Delayed Deallocation

You may have noticed the box at the left of the flowchart that mentions delayed deallocation. This is a side effect that comes with both escaping and non-escaping closures. It is not exactly a memory leak, but it might lead to undesired behavior (e.g. you dismiss a controller, but its memory doesn’t get freed up until all pending closures/operations are completed.)

Since closures, by default, will strongly capture any objects referenced in their body, this means they will impede these objects from getting deallocated from memory for as long as the closure body or scope is alive.

The lifetime of the closure scope can range from under a millisecond up to several minutes or more.

Here are some scenarios that can keep the scope alive:

There are probably other cases I have missed, but this should at least give you an idea of what might happen. Here’s an example from my demo app showing URLSession delaying deallocation:

Let’s break down the example above:

Based on the last point above, this task should not cause a strong reference cycle. However, if you run the demo app with the above scenario, and then dismiss the controller without canceling that download task, you will receive an alert saying the controller memory was not freed.

So what exactly happened here?

We are running into Scenario #4 from the list mentioned earlier. That is, we have an escaping closure that is expecting to be called back, and we gave it a long timeout interval. This closure keeps a strong reference to any objects referenced inside it ( self in this case), until it gets called or reaches the timeout deadline, or if the task gets canceled.

(I’m not sure how URLSession works behind the scenes, but I’m guessing it keeps a strong reference to the task until it gets executed, canceled or reaches the deadline.)

There is no strong reference cycle here, but this closure will keep self alive for as long as it needs it, thereby potentially delaying self ’s deallocation if the controller gets dismissed while the download task is still pending.

Using [weak self] (along with optional chaining or guard let syntax) would prevent the delay, allowing self to get deallocated immediately. [unowned self] on the other hand, would cause a crash here.

‘guard let self = self’ vs Optional Chaining

In closures that can delay deallocation due to expensive serial work, or due to a thread blocking mechanism like a semaphore (scenarios #1 and #2 in the list mentioned earlier), using guard let self = self else < return >at the start of the closure would not prevent this deallocation delay.

To illustrate why let’s say we have a closure that performs several expensive operations in succession on a UIImage:

If we do not use guard let syntax, and instead use optional chaining with self?. notation to access the methods on self, the nil check for self would happen at every method call instead of creating a strong reference at the start. This means that if self happens to be nil at any point during the execution of the closure, it will silently skip that method call and go to the next line.

It’s a rather subtle difference, but I think it’s worth pointing out for cases where you may want to avoid unnecessary work after a view controller gets dismissed, and on the flip side for cases where you want to ensure that all the work gets completed before an object gets deallocated (e.g. to prevent data corruption.)

Examples

I will go through some examples from the demo app showing common situations where [weak self] may or may not be needed.

Grand Central Dispatch

GCD calls generally do not pose a risk of reference cycles, unless they are stored to be run later.

However, the following DispatchWorkItem will cause a leak, because we are storing it in local property, and referencing self inside the closure without the [weak self] keyword:

UIView.Animate and UIViewPropertyAnimator

Similar to GCD, animation calls generally do not pose a risk of reference cycles, unless you store a UIViewPropertyAnimator in a property.

For example, these calls are safe:

The following method, on the other hand, will cause a strong reference cycle, because we are storing the animation for later use without using [weak self] :

Storing a function in a property

The following example demonstrates a cunning memory leak that can hide in plain sight.

It can be useful to pass closures or functions of one object to a different object, to be stored in a property. Let’s say you want object A to call some method from object B anonymously, without exposing object B to A. Think of it like a lightweight alternative to delegation.

As an example, here we have a presented controller that stores a closure in a property:

We also have a main controller (which owns the above controller), and we want to pass one of the main controller’s methods to be stored in the presented controller’s closure:

printer() is a function on the main controller, and we assigned this function to the closure property. Notice how we didn’t include the () parentheses in line 6 because we are assigning the function itself, not the return value of the function. Calling the closure from inside the presented controller will now print the main’s description.

To break the cycle, we need to modify setupClosure to include [weak self]

Note that we are including the parentheses after printer this time, because we want to call that function inside the scope.

Timers

Timers are interesting, as they can cause issues even if you don’t store them in a property. Let’s take this timer for example:

As long as these two conditions are met, the timer will prevent the referenced controller/objects from deallocating. So technically, this is more of a delayed allocation rather than a memory leak; the delay just happens to last indefinitely.

Be sure to invalidate your timers when they’re no longer needed to avoid keeping their referenced objects alive indefinitely and don’t forget to use [weak self] if any of the referenced objects keep a strong reference to the timer.

Demo App

There are other examples in the demo app, but I think this article is already long enough as it is, so I won’t cover all of them. I encourage you to clone the app and open it in Xcode, then check out the different leak scenarios in PresentedController.swift (I added comments explaining each scenario.)

Notice how when you run the app with a leaky scenario, your app memory usage will steadily increase as you present and dismiss controllers

retain cycle swift что это. Смотреть фото retain cycle swift что это. Смотреть картинку retain cycle swift что это. Картинка про retain cycle swift что это. Фото retain cycle swift что это

Alternatives to [weak self]

Before I conclude, I just want to mention a couple of tricks that you can use if you don’t want to bother dealing with [weak self] (I learned about them from these excellent articles by objc.io and swiftbysundell)

Let’s say we wanted to access the view property on self inside an animation closure. Here’s what that could look like:

If you want to reference multiple properties on self in the closure, you can group them all together in a tuple (let’s call it context), then pass that context to the closure:

Conclusion

Congratulations on making it this far! The article ended up being far longer than I originally planned for 😅

Here are some key takeaways:

I think the flowchart I posted earlier also provides a helpful recap of when to use [weak self]

Update: I revisted the topic of [weak self] in my new post here, which covers nested closures in Swift.

Источник

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *