Я не знаю, как Apple считает память в инструментах Xcode. Возможно, внутри себя инструменты смотрят на то, что на объект нет сильных ссылок и считают память под него освобожденной. Но в саму операционную систему она еще не вернулась.
Или представьте такой случай: strong счетчик обнулился и мы освободили память. При этом еще есть unowned ссылки и нужно отслеживать некорректное обращение к ним. И в следующий момент операционная система может что угодно записать в память, где лежит счетчик ссылок. Тогда алгоритм подсчета ссылок сломается и если где-то в коде будет обращение к деинициализированной unowned переменной, рантайм не сообщит нам точно об этом. Просто операционная система увидит, что мы обращаемся к освобожденной памяти и выдаст EXC_BAD_ACCESS.
Когда я исследовал алгоритм работы счетчика, я тоже не мог представить, что объект живет так долго) И таких неочевидных моментов в рантайме языка, я думаю, еще много.
Для пользователя выглядит так, что объекта даже физически нет в памяти. Но чтобы обращаться к счетчику ссылок в рантайме, нужно держать память под него. Иначе счетчик пришлось бы выносить куда-то за пределы объекта. И в этом случае уже можно спокойно освобождать память под объект.
Лично я уже давно интересуюсь темой компиляторов, но никогда об этом не писал на хабр. А насчет применения в проекте - иногда приходят креши с очень странными стектрейсами. У нас был случай, когда в одной структуре мы использовали безобидный property wrapper и на проде приложение падало. Я собрал релизную сборку на своей машине и поймал этот креш. И уже через пару часов выяснилось, что компилятор сгенерировал вызов swift_release для поля структуры с типом String. Причем, над этим полем не было property wrapper и на сборке в оптимизациями все работает отлично. Тогда мы просто убрали этот property wrapper и перестали падать.
Именно понимание процесса подсчета ссылок помогло мне разобраться с этим крешем. Возможно, эта статья поможет еще кому-то справится с подобной проблемой. Да и просто интересно покапаться в реализации магических штуковин внутри Swift)
Спасибо за вопрос! Weak счетчик отмеряет время жизни side table. Все обращения к weak переменным происходят через side table и нужно понимать, когда можно удалить ее из памяти.
А unowned счетчик отмеряет время жизни памяти, выделенной под объект. Так как счетчик ссылок лежит внутри объекта и рантайм постоянно с ним работает, то память нельзя освобождать до момента, пока не останется никаких ссылок. По сути, после обнуления weak счетчика можно спокойно уничтожать side table - к ней уже точно не будет обращений. А обнуление unowned счетчика гарантирует то, что больше не будет обращений к памяти объекта и ее можно очищать. Можно после обнуление strong счетчика сразу освобождать память под объект. Но потом при обращении к unonwed переменной мы не получим исключение от рантайма, с информацией о том, к какой переменной мы обратились неправильно. Вместо этого мы будем видеть критическую ошибку обращения к освобожденной памяти от операционной системы. Как, собственно, сделано с unwoned переменными в Objective-C.
Надеюсь, мой комментарий помог вам понять необходимость наличия трех ссылок. Интересно, конечно, было бы спросить самих авторов этого алгоритма посчета.
Спасибо! В состоянии freed указатель на объект, который хранится в WeakReference, затирается нулями. Дальше при обращении к weak переменной рантайм проверяет, что указатель нулевой и возвращает nil. Хорошее замечание, добавлю ответ в статью!
Спасибо, наша команда старалась сделать этот материал простым для понимая! Насчет памяти - слишком затратно затирать ее нулями. Поэтому после того, как объект уже деаллоцирован, его “отпечаток” остается. Но затем этот кусок памяти может быть выделен снова. И только после этого в него записываются уже новые данные.
Я не знаю, как Apple считает память в инструментах Xcode. Возможно, внутри себя инструменты смотрят на то, что на объект нет сильных ссылок и считают память под него освобожденной. Но в саму операционную систему она еще не вернулась.
Или представьте такой случай: strong счетчик обнулился и мы освободили память. При этом еще есть unowned ссылки и нужно отслеживать некорректное обращение к ним. И в следующий момент операционная система может что угодно записать в память, где лежит счетчик ссылок. Тогда алгоритм подсчета ссылок сломается и если где-то в коде будет обращение к деинициализированной unowned переменной, рантайм не сообщит нам точно об этом. Просто операционная система увидит, что мы обращаемся к освобожденной памяти и выдаст EXC_BAD_ACCESS.
Когда я исследовал алгоритм работы счетчика, я тоже не мог представить, что объект живет так долго) И таких неочевидных моментов в рантайме языка, я думаю, еще много.
Да, HeapObject это и есть объект. И да, память под объект удаляется только после того, как unowned счетчик равен нулю. Вот ссылка на кейс в компиляторе, когда обвнуляется счетчик unowned ссылок: https://github.com/apple/swift/blob/17358e4d98f1cf0c5e8b2e8fd7bb740f663e6149/stdlib/public/runtime/HeapObject.cpp#L481. .
Для пользователя выглядит так, что объекта даже физически нет в памяти. Но чтобы обращаться к счетчику ссылок в рантайме, нужно держать память под него. Иначе счетчик пришлось бы выносить куда-то за пределы объекта. И в этом случае уже можно спокойно освобождать память под объект.
Лично я уже давно интересуюсь темой компиляторов, но никогда об этом не писал на хабр. А насчет применения в проекте - иногда приходят креши с очень странными стектрейсами. У нас был случай, когда в одной структуре мы использовали безобидный property wrapper и на проде приложение падало. Я собрал релизную сборку на своей машине и поймал этот креш. И уже через пару часов выяснилось, что компилятор сгенерировал вызов swift_release для поля структуры с типом String. Причем, над этим полем не было property wrapper и на сборке в оптимизациями все работает отлично. Тогда мы просто убрали этот property wrapper и перестали падать.
Именно понимание процесса подсчета ссылок помогло мне разобраться с этим крешем. Возможно, эта статья поможет еще кому-то справится с подобной проблемой. Да и просто интересно покапаться в реализации магических штуковин внутри Swift)
Спасибо за вопрос! Weak счетчик отмеряет время жизни side table. Все обращения к weak переменным происходят через side table и нужно понимать, когда можно удалить ее из памяти.
А unowned счетчик отмеряет время жизни памяти, выделенной под объект. Так как счетчик ссылок лежит внутри объекта и рантайм постоянно с ним работает, то память нельзя освобождать до момента, пока не останется никаких ссылок.
По сути, после обнуления weak счетчика можно спокойно уничтожать side table - к ней уже точно не будет обращений. А обнуление unowned счетчика гарантирует то, что больше не будет обращений к памяти объекта и ее можно очищать.
Можно после обнуление strong счетчика сразу освобождать память под объект. Но потом при обращении к unonwed переменной мы не получим исключение от рантайма, с информацией о том, к какой переменной мы обратились неправильно. Вместо этого мы будем видеть критическую ошибку обращения к освобожденной памяти от операционной системы. Как, собственно, сделано с unwoned переменными в Objective-C.
Надеюсь, мой комментарий помог вам понять необходимость наличия трех ссылок. Интересно, конечно, было бы спросить самих авторов этого алгоритма посчета.
Спасибо! В состоянии freed указатель на объект, который хранится в WeakReference, затирается нулями. Дальше при обращении к weak переменной рантайм проверяет, что указатель нулевой и возвращает nil. Хорошее замечание, добавлю ответ в статью!
Спасибо, наша команда старалась сделать этот материал простым для понимая! Насчет памяти - слишком затратно затирать ее нулями. Поэтому после того, как объект уже деаллоцирован, его “отпечаток” остается. Но затем этот кусок памяти может быть выделен снова. И только после этого в него записываются уже новые данные.