Во время вызова метода объект захвачен сильным non-escaping-замыканием: до какого момента ARC обязан сохранять экземпляр?
При сильном захвате non-escaping-замыкание сохраняет экземпляр живым как минимум до завершения своего вызова. После возврата из вызова замыкание больше не может удерживать объект, поэтому экземпляр может стать доступен для уничтожения, если других сильных ссылок не осталось.
Это не означает, что deinit обязательно выполнится синхронно сразу после возврата: точный момент освобождения может зависеть от оптимизаций ARC и наличия других временных сильных ссылок.
Замыкания позволяют передавать поведение вместе с захваченным состоянием. Без автоматического управления памятью разработчику пришлось бы вручную отслеживать, сколько времени живёт это состояние и когда безопасно освобождать его.
ARC связывает время жизни захваченных ссылок с временем жизни самого замыкания. Для non-escaping-замыкания это особенно важно: компилятор знает, что замыкание не переживёт вызов функции, поэтому захват не обязан сохраняться после его завершения.
Сильный захват объекта продлевает его время жизни. Ошибочное предположение, что non-escaping-замыкание обязательно создаёт долгожившее удержание, приводит к поиску несуществующих утечек и неоправданному использованию weak.
Обратная ошибка тоже опасна: если объект захвачен слабо, само выполнение non-escaping-замыкания не гарантирует его существование. К моменту вызова слабая ссылка уже может быть автоматически обнулена.
Non-escaping-замыкание передаётся функции и не сохраняется для последующего выполнения: оно не может быть возвращено, записано в escaping-свойство или запущено после завершения вызова функции. При обычном захвате ссылочного типа замыкание владеет экземпляром сильной ссылкой.
Пока выполняется тело non-escaping-замыкания, эта сильная ссылка должна оставаться действительной. Если до вызова других сильных ссылок нет, захват замыкания временно продлевает жизнь объекта; после завершения тела замыкание уничтожается или освобождает захваченное состояние.
Минимальный пример:
Во время use экземпляр доступен благодаря сильному захвату. После выхода из блока do и прекращения действия остальных сильных ссылок он может быть освобождён.
Важно отличать non-escaping-замыкание от escaping-замыкания. Escaping-замыкание может сохраниться в свойстве, очереди или задаче и потому удерживать захваченный объект значительно дольше завершения текущего метода; non-escaping-замыкание таким образом продлить жизнь после вызова не может.
Использование weak для non-escaping-замыкания оправдано только при особой семантике: например, если выполнение должно быть пропущено, когда объект уже исчез. В противном случае слабый захват может сделать результат зависимым от момента уничтожения объекта без необходимости.
Метод форматирования принимает non-escaping-замыкание для чтения состояния модели. Разработчик добавляет [weak model], опасаясь цикла ссылок, хотя метод не сохраняет замыкание. В результате модель может исчезнуть до выполнения блока, и операция неожиданно ничего не делает.
Рассматривались два варианта. Сильный захват проще и гарантирует доступ к модели во время вызова, но временно продлевает её жизнь. Слабый захват не продлевает жизнь, однако требует обработки nil и может привести к пропуску операции.
Выбран сильный захват, потому что замыкание non-escaping, цикл с владельцем не образуется, а операция должна завершиться над существующей моделью. В результате код стал детерминированным, а долгосрочного удержания после возврата метода не возникло.
Само по себе non-escaping-замыкание не создаёт долгожившего цикла: оно не может сохраниться после завершения вызова. Цикл возможен только при наличии другого долгоживущего владельца замыкания или косвенного хранения, например если переданный callback фактически помещается в escaping-контекст.
deinit после завершения замыкания?Нет. Он гарантирует, что захваченный объект не будет уничтожен из-за отсутствия этой ссылки до окончания выполнения замыкания. После освобождения захвата объект лишь становится доступным для уничтожения; другие сильные ссылки, временные значения и оптимизации ARC могут изменить фактический момент вызова deinit.
Слабый захват не увеличивает счётчик сильных ссылок и не продлевает время жизни объекта. Если объект уничтожен до выполнения блока, weak-ссылка будет равна nil; обращение через неё даст nil или потребует явной проверки, поэтому выполнение операции может быть пропущено.