В Swift 6.4 появилась новая диагностика, которая помогает ловить неочевидные ретейн циклы. Предупреждение срабатывает, когда вложенное замыкание выполняет слабый захват, а родительское замыкание при этом неявно захватывает объект как сильную ссылку.
На первый взгляд это может показаться избыточным. Зачем указывать слабый захват дважды? Но на практике это очень полезное предупреждение, которое помогает избежать распространенного типа ошибок управления памятью.
В чем суть проблемы:
Рассмотрим пример. Внутри замыкания, переданного в loadNearbyFriends, используется weak self. При этом родительское замыкание не захватывает self явно.
timer = Timer.scheduledTimer(
withTimeInterval: 3,
repeats: true,
block: { [service] _ in
service.loadNearbyFriends { [weak self] friends in
self?.friends = friends
}
}
)
При компиляции в Swift 6.3 это код не вызывает предупреждений. Но на самом деле здесь возникает ретейн цикл. Хотя мы не захватывали self явно, компилятор неявно вставляет сильный захват self в родительское замыкание.
В результате Timer захватывает self, который удерживает объект через свойство timer. Классический ретейн цикл.
Что изменилось в Swift 6.4:
Теперь при компиляции этого кода появляется предупреждение:
'weak' ownership of capture 'self' differs from implicitly-captured strong reference in outer scope
Предупреждение явно указывает на проблему. И это гораздо лучше, чем молчаливый ретейн цикл, который может привести к утечке памяти.
Как исправить:
Есть несколько способов:
🔹Переместить weak self в родительское замыкание. Это работает, если self не используется во внешнем замыкании.
block: { [weak self, service] _ in
service.loadNearbyFriends { friends in
self?.friends = friends
}
}
🔹Выполнить два отдельных weak self захвата. Это подходит, если внешнее замыкание действительно использует self.
block: { [weak self] _ in
guard let self else { return }
logScanningDidStart()
service.loadNearbyFriends { [weak self] friends in
self?.friends = friends
}
}
🔹Явно добавить self в список захвата родительского замыкания, если сильный захват не приводит к ретейн циклу. Например, при создании Task.
Task { [self] in
guard await !cache.isValid else { return }
service.loadNearbyFriends { [weak self] friends in
self?.friends = friends
}
}
В этом случае сильный захват безопасен, потому что после проверки кеша и вызова loadNearbyFriends замыкание будет освобождено.
🔗 Читать подробнее
💡 Вывод:
Swift 6.4 добавляет предупреждение для слабого захвата во вложенных замыканиях, когда родительское замыкание неявно захватывает объект как сильную ссылку.
Это помогает ловить неочевидные ретейн циклы, которые раньше оставались незамеченными. Диагностика требует большей явности в коде, но это оправдано. Ретейн циклы - слишком частая проблема, чтобы оставлять их без внимания.
Swift продолжает развиваться в сторону безопасности. Новые предупреждения помогают писать более надежный код и избегать типичных ошибок.
Подписаться на канал:
➡️ Telegram | Max