Retain cycle swift что это

Списки захвата в Swift: в чём разница между ссылками weak, strong и unowned?

Retain cycle swift что это. olxazojaea9ikajugulkcm6c 24. Retain cycle swift что это фото. Retain cycle swift что это-olxazojaea9ikajugulkcm6c 24. картинка Retain cycle swift что это. картинка olxazojaea9ikajugulkcm6c 24
Джозеф Райт, «Пленный» — иллюстрация «сильного» захвата

Список «захваченных» значений находится перед списком параметров замыкания и может «захватить» значения из области видимости тремя разными способами: используя ссылки «strong», «weak» или «unowned». Мы часто его используем, главным образом для того, чтобы избежать циклов сильных ссылок («strong reference cycles» aka «retain cycles»).
Начинающему разработчику бывает сложно принять решение, какой именно применить способ, так что вы можете потратить много времени, выбирая между «strong» и «weak» или между «weak» и «unowned», но, со временем, вы поймёте, что правильный выбор — только один.

Для начала создадим простой класс:

Затем напишем функцию, которая создаёт экземпляр класса Singer и возвращает замыкание, которое вызывает метод playSong() класса Singer:

Наконец, мы можем где угодно вызвать sing(), чтобы получить результат выполнения playSong()

В результате будет выведена строка «Shake it off!».

«Сильный» захват (strong capturing)

До тех пор, пока вы явно не указываете способ захвата, Swift использует «сильный» захват. Это означает, что замыкание захватывает используемые внешние значения и никогда не позволит им освободиться.

Давайте опять взглянем на функцию sing()

Константа taylor определена внутри функции, так что при обычных обстоятельствах занимаемое ей место было бы освобождено как только функция закончила свою работу. Однако эта константа используется внутри замыкания, что означает, что Swift автоматически обеспечит её присутствие до тех пор, пока существует само замыкание, даже после окончания работы функции.
Это «сильный» захват в действии. Если бы Swift позволил освободить taylor, то вызов замыкания был бы небезопасен — его метод taylor.playSong() больше невалиден.

«Слабый» захват (weak capturing)

Swift позволяет нам создать «список захвата«, чтобы определить, каким именно образом захватываются используемые значения. Альтернативой «сильному» захвату является «слабый» и его применение приводит к следующим последствиям:

1. «Слабо» захваченные значения не удерживаются замыканием и, таким образом, они могут быть освобождены и установлены в nil.

2. Как следствие первого пункта, «слабо» захваченные значения в Swift всегда optional.
Мы модифицируем наш пример с использованием «слабого» захвата и сразу же увидим разницу.

[weak taylor] — это и есть наш «список захвата«, специальная часть синтаксиса замыкания, в которой мы даём инструкции о том, каким именно образом должны быть захвачены значения. Здесь мы говорим, что taylor должен быть захвачен «слабо», поэтому нам необходимо использовать taylor?.playSong() – теперь это optional, потому что может быть установлен в nil в любой момент.

Если вы теперь выполните этот код, вы увидите, что вызов singFunction() больше не приводит к выводу сообщения. Причина этого в том, что taylor существует только внутри sing(), а замыкание, возвращаемое этой функцией, не удерживает taylor «сильно» внутри себя.

Теперь попробуйте изменить taylor?.playSong() на taylor!.playSong(). Это приведёт к принудительной распаковке taylor внутри замыкания, и, соответственно, к фатальной ошибке (распаковка содержимого, содержащего nil)

«Бесхозный» захват (unowned capturing)

Альтернативой «слабому» захвату является «бесхозный».

Этот код закончится аварийно схожим образом с принудительно развернутым optional, приведенным несколько выше — unowned taylor говорит: «Я знаю наверняка, что taylor будет существовать все время жизни замыкания, так что мне не нужно удерживать его в памяти». На самом деле taylor будет освобождён практически немедленно и этот код закончится аварийно.

Так что используйте unowned крайне осторожно.

Частые возможные проблемы

Есть четыре проблемы, с которыми сталкиваются разработчики при использования захвата значений в замыканиях:

1. Сложности с расположением списка захвата в случае, когда замыкание принимает параметры

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

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

Попытка поместить список захвата после параметров замыкания приведёт к ошибке компиляции.

2. Возникновение цикла сильных ссылок, приводящее к утечке памяти

Когда сущность A обладает сущностью B и наоборот — у вас ситуация, называемая циклом сильных ссылок («retain cycle»).

В качестве примера рассмотрим код:

Мы определили класс House, который содержит одно свойство (замыкание), один метод и деинициалайзер, который выведет сообщение при уничтожении экземпляра класса.

Теперь создадим класс Owner, аналогичный предыдущему, за исключением того, что его свойство-замыкание содержит информацию о доме.

Теперь создадим экземпляры этих классов внутри блока do. Нам не нужен блок catch, но использование блока do обеспечит уничтожение экземпляров сразу после >

В результате будут выведены сообщения: “Creating a house and an owner”, “I’m dying!”, “I’m being demolished!”, затем “Done” – всё работает, как надо.

Теперь создадим цикл сильных ссылок.

Теперь появятся сообщения “Creating a house and an owner”, затем “Done”. Деинициалайзеры не будут вызваны.

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

Чтобы исправить ситуацию, нам нужно создать новое замыкание и использовать «слабый» захват в одном или двух случаях, вот так:

Нет необходимости объявлять оба значения захваченным, достаточно сделать и в одном месте — это позволит Swift уничтожить оба класса, когда это необходимо.

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

3. Непредумышленное использование «сильных» ссылок, обычно при захвате нескольких значений

Swift по умолчанию использует «сильный» захват, что может приводить к непредусмотренному поведению.
Рассмотрим следующий код:

Теперь у нас два значения захвачены замыканием, и оба их мы используем одинаковым образом. Однако, только taylor захвачен как unowned – adele захвачена сильно, потому что ключевое слово unowned должно использоваться для каждого захватываемого значения.

Если вы сделали это намеренно, то всё в порядке, но, если вы хотите, чтобы оба значения были захвачены «unowned«, вам нужно следующее:

4. Копирование замыканий и разделение захваченных значений

Последний случай, на котором спотыкаются разработчики, это то, каким образом замыкания копируются, потому что захваченные ими данные становятся доступными для всех копий замыкания.
Рассмотрим пример простого замыкания, которое захватывает целочисленную переменную numberOfLinesLogged, объявленную снаружи замыкания, так что мы можем увеличивать её значение и распечатывать его всякий раз при вызове замыкания:

Это выведет сообщение “Lines logged: 1”.
Теперь мы создадим копию замыкания, которая разделит захваченные значения вместе с первым замыканием. Таким образом, вызываем мы оригинальное замыкание или его копию, мы увидим растущее значение переменной.

Это выведет сообщения “Lines logged: 1”. “Lines logged: 4”, потому что logger1 и logger2 указывают на одну и туже захваченную переменную numberOfLinesLogged.

В каких случаях использовать «сильный» захват, «слабый» и «бесхозный»

Теперь, когда мы понимаем, как всё работает, попробуем подвести итог:

1. Если вы уверены, что захваченное значение никогда не станет nil при выполнении замыкания, вы можете использовать «unowned capturing». Это нечастая ситуация, когда использование «слабого» захвата может вызвать дополнительные сложности, даже при использовании внутри замыкания guard let к слабо захваченному значению.

2. Если у вас случай цикла сильных ссылок (сущность А владеет сущностью B, а сущность B владеет сущностью А), то в одном из случаев нужно использовать «слабый» захват («weak capturing»). Необходимо принять во внимание, какая из двух сущностей будет освобождена первой, так что если view controller A представляет view controller B, то view controller B может содержать «слабую» ссылку назад к «А».

3. Если возможность цикла сильных ссылок исключена, вы можете использовать «сильный» захват («strong capturing»). Например, выполнение анимации не приводит к блокированию self внутри замыкания, содержащего анимацию, так что вы можете использовать «сильное» связывание.

4. Если вы не уверены, начните со «слабого» связывания и измените его только в случае необходимости.

Источник

Что нужно знать об ARC

ARC — не сборщик мусора

Код без ARC:
Код с ARC:

Сильные и слабые ссылки

Вместо атрибутов retain и assign для properties в ARC используются атрибуты strong и weak (__strong и __weak для локальных переменных и ivars). Это не совсем их аналоги, но об этом позже. Атрибут strong используется по умолчанию, так что указывать его явно не обязательно.

Properties, переменные которых ссылаются на объекты «вверх» по иерархии и делегаты должны быть слабыми, чтобы избежать циклических ссылок.

Поскольку локальные переменные по умолчанию сильные (__strong), возможны такие конструкции в коде:

Объект, сохраненный в переменной originalDate будет жив до тех пор, пока ARC не найдет последнюю строку, где используется эта переменная, а затем тут же ее освободит (подставит release).

Для создания слабой ссылки используется атрибут __weak:

В данном примере originalDate может перестать существовать уже на второй строчке, если на этот объект больше нет сильных ссылок.

Важная и полезная особенность ARC: сразу после деаллокации слабые ссылки обнуляются, то есть становятся равными nil.

А поскольку в Objective-C nil может принимать любые сообщения, проблемы с EXC_BAD_ACCESS уходят в прошлое. Это и есть отличие от атрибутов retain/assign. Подобное происходит и при объявлении объектов: они неявно инициализируются nil’ом. Полезным приёмом является кэширование слабых property в сильных локальных переменных, чтобы быть уверенным, что объект будет жив необходимое время:

По этой же причине при проверке слабых property на nil, например, делегатов, нужно их кэшировать:

Присваивание кэшированному объекту nil убирает сильную ссылку, и если на него больше нет других сильных ссылок, объект будет деаллоцирован.

В редких случаях нужно, чтобы ссылки на объект после его уничтожения не обнулялись. Для этого существует атрибут unsafe_unretained:

Autorelease

Объекты, созданные с помощью статических методов (например: [NSData data. ], [NSArray array. ] и т. д.) и литералов (@«string», @42, @[], @<> ) больше не авторелизные. Время жизни таких обьектов задается только сильными ссылками на них.
Существует атрибут __autoreleasing, который, согласно документации, рекомендуется использовать для двойных указателей (* id) в случае, если необходимо передать результат в параметр.

Tогда метод save должен иметь следующую сигнатуру:

Это гарантирует сохранность созданного внутри метода объекта. Если объявить переменную без этого атрибута, то компилятор создаст временную авторелизную переменную, которую передаст в метод:

NSAutoreleasePool теперь недоступен для прямого использования. Вместо него предлагается использовать директиву @autoreleasepool <>. При входе в такой блок состояние пула сохраняется, при выходе восстанавливается, освобождая все объекты, созданные внутри. Рекомендуется использовать @autoreleasepool в циклах, если создается большое количество временных обьектов.

Блоки

Блоки по-прежднему необходимо копировать.

О циклических ссылках компилятор предупредит:

warning: capturing ‘self’ strongly in this block is likely to lead to a retain cycle
[-Warc-retain-cycles,4]

Когда блок будет скопирован, он также захватит self, если в нем используются instance varisbles.

Чтобы избежать таких случаев, нужно создать слабую ссылку на self:

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

Строим мосты

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

Заключение

Используйте ARC. Это проще, безопаснее и сэкономит вам время и нервы.

Источник

Списки захвата в Swift: в чём разница между ссылками weak, strong и unowned?

Retain cycle swift что это. olxazojaea9ikajugulkcm6c 24. Retain cycle swift что это фото. Retain cycle swift что это-olxazojaea9ikajugulkcm6c 24. картинка Retain cycle swift что это. картинка olxazojaea9ikajugulkcm6c 24
Джозеф Райт, «Пленный» — иллюстрация «сильного» захвата

Список «захваченных» значений находится перед списком параметров замыкания и может «захватить» значения из области видимости тремя разными способами: используя ссылки «strong», «weak» или «unowned». Мы часто его используем, главным образом для того, чтобы избежать циклов сильных ссылок («strong reference cycles» aka «retain cycles»).
Начинающему разработчику бывает сложно принять решение, какой именно применить способ, так что вы можете потратить много времени, выбирая между «strong» и «weak» или между «weak» и «unowned», но, со временем, вы поймёте, что правильный выбор — только один.

Для начала создадим простой класс:

Затем напишем функцию, которая создаёт экземпляр класса Singer и возвращает замыкание, которое вызывает метод playSong() класса Singer:

Наконец, мы можем где угодно вызвать sing(), чтобы получить результат выполнения playSong()

В результате будет выведена строка «Shake it off!».

«Сильный» захват (strong capturing)

До тех пор, пока вы явно не указываете способ захвата, Swift использует «сильный» захват. Это означает, что замыкание захватывает используемые внешние значения и никогда не позволит им освободиться.

Давайте опять взглянем на функцию sing()

Константа taylor определена внутри функции, так что при обычных обстоятельствах занимаемое ей место было бы освобождено как только функция закончила свою работу. Однако эта константа используется внутри замыкания, что означает, что Swift автоматически обеспечит её присутствие до тех пор, пока существует само замыкание, даже после окончания работы функции.
Это «сильный» захват в действии. Если бы Swift позволил освободить taylor, то вызов замыкания был бы небезопасен — его метод taylor.playSong() больше невалиден.

«Слабый» захват (weak capturing)

Swift позволяет нам создать «список захвата«, чтобы определить, каким именно образом захватываются используемые значения. Альтернативой «сильному» захвату является «слабый» и его применение приводит к следующим последствиям:

1. «Слабо» захваченные значения не удерживаются замыканием и, таким образом, они могут быть освобождены и установлены в nil.

2. Как следствие первого пункта, «слабо» захваченные значения в Swift всегда optional.
Мы модифицируем наш пример с использованием «слабого» захвата и сразу же увидим разницу.

[weak taylor] — это и есть наш «список захвата«, специальная часть синтаксиса замыкания, в которой мы даём инструкции о том, каким именно образом должны быть захвачены значения. Здесь мы говорим, что taylor должен быть захвачен «слабо», поэтому нам необходимо использовать taylor?.playSong() – теперь это optional, потому что может быть установлен в nil в любой момент.

Если вы теперь выполните этот код, вы увидите, что вызов singFunction() больше не приводит к выводу сообщения. Причина этого в том, что taylor существует только внутри sing(), а замыкание, возвращаемое этой функцией, не удерживает taylor «сильно» внутри себя.

Теперь попробуйте изменить taylor?.playSong() на taylor!.playSong(). Это приведёт к принудительной распаковке taylor внутри замыкания, и, соответственно, к фатальной ошибке (распаковка содержимого, содержащего nil)

«Бесхозный» захват (unowned capturing)

Альтернативой «слабому» захвату является «бесхозный».

Этот код закончится аварийно схожим образом с принудительно развернутым optional, приведенным несколько выше — unowned taylor говорит: «Я знаю наверняка, что taylor будет существовать все время жизни замыкания, так что мне не нужно удерживать его в памяти». На самом деле taylor будет освобождён практически немедленно и этот код закончится аварийно.

Так что используйте unowned крайне осторожно.

Частые возможные проблемы

Есть четыре проблемы, с которыми сталкиваются разработчики при использования захвата значений в замыканиях:

1. Сложности с расположением списка захвата в случае, когда замыкание принимает параметры

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

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

Попытка поместить список захвата после параметров замыкания приведёт к ошибке компиляции.

2. Возникновение цикла сильных ссылок, приводящее к утечке памяти

Когда сущность A обладает сущностью B и наоборот — у вас ситуация, называемая циклом сильных ссылок («retain cycle»).

В качестве примера рассмотрим код:

Мы определили класс House, который содержит одно свойство (замыкание), один метод и деинициалайзер, который выведет сообщение при уничтожении экземпляра класса.

Теперь создадим класс Owner, аналогичный предыдущему, за исключением того, что его свойство-замыкание содержит информацию о доме.

Теперь создадим экземпляры этих классов внутри блока do. Нам не нужен блок catch, но использование блока do обеспечит уничтожение экземпляров сразу после >

В результате будут выведены сообщения: “Creating a house and an owner”, “I’m dying!”, “I’m being demolished!”, затем “Done” – всё работает, как надо.

Теперь создадим цикл сильных ссылок.

Теперь появятся сообщения “Creating a house and an owner”, затем “Done”. Деинициалайзеры не будут вызваны.

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

Чтобы исправить ситуацию, нам нужно создать новое замыкание и использовать «слабый» захват в одном или двух случаях, вот так:

Нет необходимости объявлять оба значения захваченным, достаточно сделать и в одном месте — это позволит Swift уничтожить оба класса, когда это необходимо.

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

3. Непредумышленное использование «сильных» ссылок, обычно при захвате нескольких значений

Swift по умолчанию использует «сильный» захват, что может приводить к непредусмотренному поведению.
Рассмотрим следующий код:

Теперь у нас два значения захвачены замыканием, и оба их мы используем одинаковым образом. Однако, только taylor захвачен как unowned – adele захвачена сильно, потому что ключевое слово unowned должно использоваться для каждого захватываемого значения.

Если вы сделали это намеренно, то всё в порядке, но, если вы хотите, чтобы оба значения были захвачены «unowned«, вам нужно следующее:

4. Копирование замыканий и разделение захваченных значений

Последний случай, на котором спотыкаются разработчики, это то, каким образом замыкания копируются, потому что захваченные ими данные становятся доступными для всех копий замыкания.
Рассмотрим пример простого замыкания, которое захватывает целочисленную переменную numberOfLinesLogged, объявленную снаружи замыкания, так что мы можем увеличивать её значение и распечатывать его всякий раз при вызове замыкания:

Это выведет сообщение “Lines logged: 1”.
Теперь мы создадим копию замыкания, которая разделит захваченные значения вместе с первым замыканием. Таким образом, вызываем мы оригинальное замыкание или его копию, мы увидим растущее значение переменной.

Это выведет сообщения “Lines logged: 1”. “Lines logged: 4”, потому что logger1 и logger2 указывают на одну и туже захваченную переменную numberOfLinesLogged.

В каких случаях использовать «сильный» захват, «слабый» и «бесхозный»

Теперь, когда мы понимаем, как всё работает, попробуем подвести итог:

1. Если вы уверены, что захваченное значение никогда не станет nil при выполнении замыкания, вы можете использовать «unowned capturing». Это нечастая ситуация, когда использование «слабого» захвата может вызвать дополнительные сложности, даже при использовании внутри замыкания guard let к слабо захваченному значению.

2. Если у вас случай цикла сильных ссылок (сущность А владеет сущностью B, а сущность B владеет сущностью А), то в одном из случаев нужно использовать «слабый» захват («weak capturing»). Необходимо принять во внимание, какая из двух сущностей будет освобождена первой, так что если view controller A представляет view controller B, то view controller B может содержать «слабую» ссылку назад к «А».

3. Если возможность цикла сильных ссылок исключена, вы можете использовать «сильный» захват («strong capturing»). Например, выполнение анимации не приводит к блокированию self внутри замыкания, содержащего анимацию, так что вы можете использовать «сильное» связывание.

4. Если вы не уверены, начните со «слабого» связывания и измените его только в случае необходимости.

Источник

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

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 что это. картинка

This was pretty much me for a while:

Retain cycle swift что это. 1*gq bNPMQ54Epc8BDR3sLPw. Retain cycle swift что это фото. Retain cycle swift что это-1*gq bNPMQ54Epc8BDR3sLPw. картинка Retain cycle swift что это. картинка 1*gq bNPMQ54Epc8BDR3sLPw

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 что это. 1*yHX 8dJrQpH7R2hfM 21MQ. Retain cycle swift что это фото. Retain cycle swift что это-1*yHX 8dJrQpH7R2hfM 21MQ. картинка Retain cycle swift что это. картинка 1*yHX 8dJrQpH7R2hfM 21MQ

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 что это. 1*x43 iXLBTPrTdYpQXgu tg. Retain cycle swift что это фото. Retain cycle swift что это-1*x43 iXLBTPrTdYpQXgu tg. картинка Retain cycle swift что это. картинка 1*x43 iXLBTPrTdYpQXgu tg

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 не будет опубликован. Обязательные поля помечены *