Память растёт, но инструмент Leaks ничего не находит — что такое abandoned object graph?
При навигации память растёт и не возвращается, но инструмент Leaks сообщает ноль утечек. Инструментом Allocations вы ставите метку поколения после каждого визита на экран.
Объясните, что такое abandoned object graph, почему Leaks его не видит и какой инструмент и приём его находят. Ниже метки поколений.
Generation Growth Persistent Category (top)
Gen A +12.4 MB 12.4 MB ImageBuffer, DetailViewModel
Gen B +12.1 MB 24.5 MB ImageBuffer, DetailViewModel
Gen C +12.3 MB 36.8 MB ImageBuffer, DetailViewModel
Leaks: 0 leaked blocks
Определите причину.
Abandoned object graph — это достижимая память под strong-ссылкой, которая больше не используется, поэтому Leaks, сообщающий лишь о недостижимых блоках, её не видит. Генерационный анализ Allocations показывает ~12 МБ persistent на каждый экран — это удержанный граф.
- ✗Считают любой рост памяти без утечки фрагментацией, которую не починить
- ✗Верят, что инструмент Leaks ловит любой рост памяти
- ✗Путают заброшенный граф с retain cycle или проблемой autorelease pool
- →Почему инструмент Leaks сообщает ноль, пока память всё ещё растёт?
- →Как метка поколения на каждый экран изолирует удержанные объекты?
An abandoned object graph is memory that is still reachable — some strong reference keeps pointing at it — but that the app will never use again. The Leaks instrument only reports blocks that have become unreachable with a nonzero retain count, so a reachable-but-dead graph is invisible to it. That is why Leaks says zero while memory climbs:
Gen A +12.4 MB → 12.4 MB persistent
Gen B +12.1 MB → 24.5 MB persistent each screen visit leaves ~12 MB that never frees
Gen C +12.3 MB → 36.8 MB persistent same categories: ImageBuffer, DetailViewModel
The tool is the Allocations instrument with generational analysis: mark a generation on entering the screen and another after leaving. Objects that survive to the next generation and keep accumulating are the abandoned graph. Here DetailViewModel and its ImageBuffer persist across every generation — a cache that never evicts, or a closure/observer holding self. Break the strong reference (evict the cache, capture [weak self]) and the generations stop growing.