В Swift замыкания (closures) могут «захватывать» значения и ссылки из окружающего контекста. Если захватываемое значение — это тип со значением (struct, enum), closure копирует данные. Если захватывается ссылка (например, класс), closure хранит ссылку на экземпляр класса, что может привести к retain cycle, если не использовать [weak self] или [unowned self].
Механизм захвата позволяет реализовать асинхронные задачи и callback-и, однако неправильное использование мешает управлению памятью.
Пример:
class MyClass { var value = 10 func doSomething() { let closure = { [weak self] in print(self?.value ?? "nil") } closure() } }
В данном примере используется [weak self], чтобы избежать цикла сильных ссылок, так как действует асинхронная работа.
Вопрос: "Всегда ли использование [weak self] гарантирует отсутствие утечек памяти при захвате self в замыкании?"
Ответ: Нет, если внутри closure появляется сильная ссылка (например, через промежуточные переменные), даже с [weak self] можно получить retain cycle. Например:
let closure = { [weak self] in let strongSelf = self strongSelf?.doSomething() }
Если привязать closure к длительно живущему объекту (например, NotificationCenter), можно получить retain cycle, если за self есть другие ссылки.
История 1
В проекте iOS-приложения разработчик забыл указать
[weak self]при передаче замыкания в сетевой запрос. В результате, пока выполнялся запрос, объект view контроллера не освобождался, даже если пользователь закрыл экран. Это привело к утечкам памяти при интенсивном использовании сетевых операций.
История 2
В сложной системе подписки на таймер объект, подписавшийся через closure без использования
weak, продолжал выполнять свои действия даже после удаления экрана из памяти. Это привело к повторным вызовам UI-элементов, которых уже не было в иерархии, что вызывало крэши приложения.
История 3
При реализации кэширования данных closure захватывал ссылку на self без аннотации
[unowned self]в долгоживущем объекте кеша. После уничтожения оригинального объекта попытка обращения к self из closure вызывала обращение к уже освобождённой памяти и краш приложения.