Последние пару дней я искал больше об управлении памятью в iOS. Мне было непросто понять эту тему в Swift. Поэтому я решил немного углубиться в старый добрый Objective-C - поскольку я уже изучаю его, чтобы лучше понять управление памятью и получить более глубокое понимание этой темы. Мол, как было до ARC? Что случилось после этого? И в чем разница между ARC и сборщиком мусора?… И т. Д.

Вступление

Когда вы начнете работать с объектами, вы заметите интересную вещь.

Объекты более требовательны к памяти, чем примитивные типы, такие как целые числа и числа с плавающей запятой. Это остается верным независимо от того, происходят ли эти объекты из ваших собственных классов или они являются объектами, которые вы создаете из классов в любой из платформ Apple.

К счастью, нам не нужно беспокоиться о ручной работе с адресами памяти и ручном выделении или освобождении областей памяти (что по-прежнему верно для некоторых языков). Вместо этого в Objective-C мы используем так называемый подсчет ссылок.

Что означает подсчет ссылок?

Когда ваше приложение запускается, для ваших объектов становится доступной область памяти. Итак, вы создаете объект, и для этого объекта требуется область памяти. Теперь переменная, содержащая объект, является указателем на этот объект. Указатель на область памяти, если быть точным. Под капотом происходит то, что при создании объекта ему присваивается так называемый счетчик сохранения или счетчик ссылок, который представляет количество владельцев для конкретного объекта. Таким образом, мы можем представить его как единое целое, как вы видите на изображении ниже:

Переменная, вызвавшая var, является указателем на блок памяти. Итак, в своей программе вы работаете с этим указателем и вызываете для него методы сколько угодно долго. Но когда вы дойдете до конца блока кода. Эта переменная больше недоступна или недоступна для какой-либо части программы. Счетчик сохраненных данных снова обнуляется, потому что теперь на этот блок памяти ничего не указывает. И когда счетчик сохранения становится равным нулю, механизм выполнения сообщает, что это блок памяти, о котором никому нет дела. Следовательно, он освободит этот блок памяти и сделает его доступным для использования другими объектами.

Что может пойти не так?

Единственная проблема здесь может быть в ситуациях, когда вы создаете объекты и передаете их из одного места в другое. Поэтому неясно, находится ли указатель в области видимости или нет. И вплоть до 2011 года, когда был добавлен ARC (Автоматический подсчет ссылок), вам приходилось писать операторы, когда вы закончили использовать этот объект.

До ARC

Прежде чем ARC был добавлен в Objective-C, нам нужно было сделать что-то вроде этого

MyClass *myObject = [[MyClass alloc] init];
[myObject myMethod]; // call methods
... // doing some stuff with the object
[myObject release]; // releasing the object

Мы бы написали немного кода для создания объекта. Было бы создано. Мы бы вызывали методы этого объекта. Но в какой-то момент нам пришлось бы явно вызвать оператор release. И это было то, что отвечало за отсчет удержания.

Это не проблема с небольшим количеством объектов. Но когда у вас были сотни или тысячи объектов, которые создавались, обрабатывались, использовались в качестве параметров, передавались от объекта к объекту… вам приходилось отслеживать их все. Если вы передавали объект из одной области своей программы в другую, вы даже не можете быть уверены, что можете его выпустить. Или если какая-то другая часть программы позаботится о ее выпуске или может даже выпустить до того, как вы закончите с ней. Таким образом, вы также можете написать, что было вызвано, сохранить вызовы этого объекта. Но вам все равно нужно будет сопоставить любой вызов удержания с дополнительным вызовом освобождения.

По сути, до ARC вы должны были представить себе все возможные сценарии-логики, которые ваше приложение будет выполнять, чтобы убедиться, что все время жизни ваших объектов управляется правильно. Не все так просто.

После ARC

К счастью, при использовании ARC вам больше не нужно использовать release autorelease retain вызовы. Но важно понимать опасность неправильного написания этого кода.

Одна из проблем, с которыми вы можете столкнуться, - это то, что вы можете выпустить слишком рано. Вы создаете объект, имеете указатель на эту область памяти, вызываете методы и в какой-то момент отпускаете его. Но если у вас все еще есть действующий указатель, ничто не помешает вам написать еще одну строку кода, которая попытается его использовать. Это будет называться висячим указателем. Указатель существовал, но то, на что он указывал, больше не действовало в памяти, и это могло вызвать сбой.

Обратной стороной этого является то, что вы можете вообще не отпускать, вы можете создать объект. Начните вызывать методы этого объекта, а затем просто дайте указателю выпасть из области видимости и исчезнуть. Но вы так и не выпустили объект, поэтому вы просто начали требовать все больше и больше памяти, что было названо утечкой памяти.

Поэтому необходимость написания большого количества этого кода, безусловно, была очень подвержена ошибкам и проблемам. Теперь вы можете задаться вопросом, используем ли мы эту новую функцию ARC, почему я вообще упоминаю о существовании таких вещей, как освобождение и удержание вызовов. Поскольку идея вызовов освобождения и удержания никуда не делась, язык по-прежнему подсчитывает ссылки.

Язык не изменился. Разница в том, что вам просто не нужно писать вызовы keep, release и autorelease, потому что это делает компилятор. ARC принимает тот факт, что компиляторы стали настолько хорошими, что каждый раз, когда вы создаете свой проект, компилятор, в данном случае llvm, который Xcode использует за кулисами, может определить все возможные пути для вашего кода. . И он в основном проходит через ваш код, синтезируя вызовы записи, сохранения, освобождения и автозапуска, которые вам понадобятся.

Компилятор эффективно пишет тот же код, который вы бы написали сами, если бы вы действительно, действительно, действительно хорошо писали код управления памятью.

ARC физически не изменяет файлы исходного кода, но это фактически то, что делает компилятор, когда вы компилируете проект, а вы используете ARC.

Разница между сборщиком мусора и ARC

ARC - это совсем другой эффект, чем сборщик мусора. Языки, использующие сборку мусора, часто называют недетерминированными. Это означает, что вы не можете точно сказать, когда объекты восстанавливаются, потому что это управляется средой выполнения внешним процессом. ARC позволяет нам быть полностью детерминированными. Код контролирует, когда эти объекты будут освобождены. Просто код для их выпуска был написан компилятором, а не вами. Фактически, вы не только не записываете эти вызовы при использовании ARC, но и не можете записывать эти вызовы. Если вы попытаетесь сделать хотя бы простой вызов, вы получите ошибку.

Заключение

Вы должны осознавать идею сохранения или освобождения. Это все еще то, что происходит под капотом, но, конечно, если вам не приходилось это делать, будьте благодарны за то, что вам больше не нужно этого делать.

Мне бы хотелось услышать от вас больше информации по этой теме, и что вы думаете о том, чтобы вернуться к такому языку, как Objective-C, чтобы лучше понять такую ​​тему, как управление памятью? Вы когда-нибудь делали это раньше?