Tehnografi.com - Технологические новости, обзоры и советы

Автоматический подсчет ссылок (ARC) в Swift

Следующая статья поможет вам: Автоматический подсчет ссылок (ARC) в Swift

Вернуться в блог

Автоматический подсчет ссылок (ARC) — это механизм управления памятью в Swift. Работа с концепциями ARC имеет важное значение в повседневной разработке iOS. В этом уроке мы обсудим, как ARC используется для управления памятью в iOS.

Вот что вы узнаете:

  • Как работает автоматический подсчет ссылок
  • Почему управление памятью важно для приложений iOS
  • Как счетчик сохранения объекта влияет на освобождение
  • Практические примеры управления памятью
  • Распространенные ошибки, такие как циклы сохранения
  • Работа с сильными и слабыми ссылками.

Каждый iPhone имеет ограниченный объем оперативной памяти, в настоящее время около 4 ГБ. ОЗУ используется для временного хранения информации, когда вы используете свой iPhone. Данные, хранящиеся в оперативной памяти, не сохраняются между перезагрузками; но чтение и запись в ОЗУ происходит очень быстро.

  • БАРАН: Быстрая, но маленькая память, «краткосрочная память» компьютера.
  • Вспышка: Медленная, но большая память, «долговременная память» компьютера.

Все активные приложения на вашем iPhone и сама операционная система iOS используют доступную оперативную память. Когда одно приложение использует много памяти, другим приложениям доступно меньше ее. На уровне операционной системы в iOS есть процессы, которые делают использование памяти более эффективным. Однако мы собираемся обсудить, как вы можете управлять памятью в своем приложении.

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

Управление памятью — это процесс определения того, какую память можно удалить, чтобы освободить ОЗУ. Это непрерывный процесс: оперативная память используется для новых данных, и эти данные удаляются, когда они больше не нужны. Это, конечно, проблема, когда из памяти удаляются не те данные. Ваша задача как разработчика приложений — написать приложение таким образом, чтобы памятью можно было управлять (почти) автоматически.

В отличие от ПК/Mac, iOS не использует файл подкачки для записи редко используемой или избыточной памяти в постоянное хранилище. Флеш-накопитель на iPhone просто недостаточно быстр, чтобы его можно было использовать в качестве подкачки. Вместо этого, когда приложение iOS использует больше памяти, чем выделила для него ОС, оно попросит приложение уменьшить объем памяти или уничтожит приложение.

Основываясь на идее, что оперативная память ограничена и все приложения используют один и тот же пул, приложение должно быть «добропорядочным гражданином» и эффективно управлять памятью. Гипотетически, если приложение «Почта» использует 90% памяти, вы не сможете использовать Карты, потому что вскоре у вас закончится память.

Когда на вашем iPhone заканчивается память, iOS закрывает приложения, которые используют слишком много памяти. Это ухудшает пользовательский опыт – вам как разработчику этого не хотелось бы!

На мобильных платформах Apple уделяет большое внимание длительному сроку службы батареи. Хранение данных в оперативной памяти требует значительного количества энергии. Вы можете себе представить, что приложение, которое использует слишком много памяти или больше, чем необходимо, увеличивает срок службы батареи. Когда это приложение закрывается, ему может потребоваться записать свое состояние в постоянное флэш-хранилище, что влечет за собой дополнительные затраты процессора и энергии.

Зачем изучать управление памятью на iOS?

  1. Это весело
  2. Чтобы избежать циклов сохранения

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

В iOS вы используете автоматический подсчет ссылок (ARC) для управления памятью, о чем мы поговорим далее. На платформах Android вы используете сборку мусора (GC) для управления памятью.

Короче говоря, сбор мусора — это отдельный процесс ОС, который удаляет неиспользуемые данные из памяти. Это часто приводит к снижению производительности, поскольку процессу сборки мусора приходится определять, какую память следует удалить («пометить и очистить»).

У ARC нет этого узкого места, поскольку его код управления памятью является частью приложения (а не другого процесса). Недостатком ARC является то, что он требует больше усилий от разработчика, а при использовании ARC всегда существует риск плохого управления памятью и циклами сохранения.

Давайте обсудим, как работает автоматический подсчет ссылок (ARC). Давайте кратко рассмотрим основные принципы ARC.

  • Цель автоматического подсчета ссылок — узнать, какие данные можно удалить из оперативной памяти, чтобы освободить место.
  • Важнейшей концепцией ARC является счетчик удерживания, который представляет собой число, которое отслеживает, сколько объектов «удерживается» за другим объектом.
  • ARC применяется только к ссылочным типам, таким как классы, а не к типам значений, таким как структуры. Типы значений копируются, поэтому они не работают со ссылками.

Вы уже можете видеть, откуда взялось название «Автоматический подсчет ссылок».

  • Речь идет о ссылках; связь между объектами в вашем коде.
  • Речь идет также о счете; отслеживание количества сохранений объекта.
  • И речь идет об автоматизации; вы как разработчик хотите тратить минимум усилий на управление памятью.

До 2011 года, когда в iOS 5 и Xcode 4.2 был представлен автоматический подсчет ссылок (ARC), разработчикам приходилось выделять и освобождать память вручную. Вы можете использовать alloc, save и Release, чтобы указать, какие объекты необходимо сохранить или освободить. Метод сохранения увеличит количество сохранений объекта, а метод Release уменьшит его. Как и в случае с ARC, нулевой счетчик сохранения означал, что объект был удален из памяти. Сегодня ARC автоматизирует этот процесс.

Каждый объект в Swift (экземпляр класса) имеет свойство, называемое continueCount.

  • Когда счетчик сохранения больше нуля, объект сохраняется в памяти.
  • Когда счетчик сохранения достигает нуля, объект удаляется из памяти.

Как мы уже говорили, только ссылочные типы, такие как классы, имеют счетчик сохранения. Тип значения, например структура, копируется при передаче в коде; нет ничего, на что можно было бы ссылаться или сохранять. ARC влияет только на экземпляры, которые являются «общими» или передаются по ссылке в вашем коде.

Прежде чем двигаться дальше, давайте рассмотрим аналогию. Представьте себе сценарий, в котором трое человек – Берг, Пит и Шэрон – делят лист бумаги с написанным на нем номером телефона. Мы звоним этому листу бумаги с номером телефона. Разумеется, статья является экземпляром класса Swift.

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

На данный момент количество сохраненных бумаг равно 3. Почему? Потому что бумажный предмет держат три человека. Это имеет последствие: бумажный объект невозможно удалить из памяти, поскольку он все еще используется.

В какой-то момент Шэрон больше не держит в руках бумажку с номером телефона. Пит тоже отпускает бумагу. Например, они закончили звонить по номеру телефона. Счетчик удержаний сокращается до 2, а затем до 1. Бумагу держит только Берг. Данные еще не удалены из памяти.

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

Давайте обсудим, как это работает на практике в программировании на Swift.

Технически, когда вы создаете объект определенного типа класса, этот объект называется экземпляром, например, «экземпляром класса». Это общепринятая терминология принципов объектно-ориентированного программирования. В современном программировании и в дальнейшем мы часто называем «экземпляры» «объектами».

Мы собираемся взглянуть на ARC в действии, чтобы вы могли увидеть, как он работает. Сначала мы создадим простой класс под названием Car. Так:

класс автомобиля
{
имя вар = «»

инициализация (имя: Строка) {
self.name = имя
print(“Экземпляр автомобиля \(имя) инициализирован”)
}

деинит {
print(“Экземпляр Car деинициализируется”)
}
}

Этот класс Car имеет одно имя свойства типа String. Мы также определили две функции, называемые init и deinit. Эти функции вызываются при инициализации и деинициализации экземпляра Car. Они помогут нам увидеть, когда это произойдет.

Затем мы объявим две переменные:

вар car_1:Машина?
вар car_2:Машина?

Их тип — Автомобиль?, поэтому они не являются обязательными. Обе переменные еще не инициализированы.

Далее мы инициализируем car_1, назначив ему экземпляр класса Car. Мы также установим его свойство name. Так:

car_1 = Автомобиль(название: «Мазерати»)

Пока все хорошо, да? Мы только что создали переменную и присвоили ей экземпляр Car. Логически вывод кода будет следующим:

Экземпляр Car инициализируется

Затем мы назначим car_1 машине car_2. Так:

машина_2 = машина_1

И car_1, и car_2 теперь используют ссылку на один и тот же экземпляр Car. Вот как это работает в Swift — мы скопировали ссылку, но не сам объект. И car_1, и car_2 теперь ссылаются на один и тот же объект!

Теперь вот что становится интересным. Мы делаем это:

автомобиль_1 = ноль

Переменной car_1 присвоено значение nil, поэтому переменная больше не содержит ссылку на экземпляр Car. Какова сейчас стоимость car_2?

Переменная car_2 по-прежнему ссылается на экземпляр Car, хотя мы обнулили переменную car_1. Экземпляр Car сохраняется в памяти и еще не освобожден, поскольку car_2 все еще удерживает его.

Наконец, мы делаем это:

автомобиль_2 = ноль

Вывод теперь:

Экземпляр Car инициализируется
Экземпляр Car деинициализируется

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

Основная концепция, которую мы здесь видели на практике, — это сильная ссылка. В этом примере и car_1, и car_2 содержат строгую ссылку на экземпляр Car.

Сильная ссылка увеличит количество сохранений объекта, когда на него ссылаются. В результате объект будет сохраняться в памяти, даже если другие ссылки обнулятся, до тех пор, пока ссылки не исчезнут, после чего объект будет удален из памяти. Вот как работает ARC.

Не стесняйтесь поиграть с песочницей ниже. Вы можете опустить последнюю строку, car_2 = nil, чтобы увидеть, когда экземпляр Car будет освобожден (или нет).

класс автомобиля
{
имя вар = «»

инициализация (имя: Строка) {
self.name = имя
print(“Экземпляр автомобиля \(имя) инициализирован”)
}

деинит {
print(“Экземпляр автомобиля \(имя) деинициализируется”)
}
}

вар car_1:Машина?
вар car_2:Машина?

car_1 = Автомобиль(название: «Мазерати»)

машина_2 = машина_1

автомобиль_1 = ноль
автомобиль_2 = ноль

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

Мы также обсудили, как такой код, как инициализатор Car(), создает объект и сохраняет его в памяти. Это называется выделением, поскольку часть оперативной памяти выделяется для хранения объекта «Автомобиль».

Учитывая это, когда именно освобождается память для Car? Когда пространство памяти очищается и освобождается для других данных? Более того, в предыдущем примере мы явно установили car_1 и car_2 равными нулю, чего вы обычно не делаете в повседневном кодировании, по крайней мере, не для всех объектов.

Что произойдет с сильной ссылкой, если ей явно не присвоено значение ноль?

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

Взгляните на следующую функцию:

func rentCar (тип: строка)
{
вар аренда = Автомобиль()
rent.type = тип

// Едем на машине, возвращаем ее и ничего не делаем
аренда.диск()
}

В приведенной выше функции мы создаем новый арендованный автомобиль определенного типа. Затем мы едем на нем, возвращаем его прокатной компании и отправляемся домой. Теперь спросите себя: когда экземпляр Car освобождается?

Если вы помните о счетчике сохранения, вы можете утверждать, что экземпляр Car освобождается в конце функции rentCar(). Вот почему:

  • Счетчик сохранения для экземпляра Car увеличивается с 0 до 1, когда на него ссылается переменная аренды.
  • Строка типа является типом значения, поэтому она никак не влияет на количество сохраненных арендных платежей.
  • Насколько мы видим, функция Drive() не имеет побочных эффектов, поэтому она также не влияет на количество сохранений.
  • В конце функции переменная rent больше не нужна – область действия функции перестает существовать – поэтому аренда (неявно) устанавливается в ноль, а объект Car освобождается и освобождается из памяти.

Никакой другой код не содержит сильной ссылки на аренду, и экземпляр не «ускользает» от функции rentCar(); это не передается. В результате экземпляр Car освобождается из памяти.

Что произойдет, если арендованная недвижимость станет собственностью другого класса? Это меняет все. Проверь это:

класс РенталКомпани
{
вар прокат:Машина?

func rentCar (тип: строка)
{
аренда = Автомобиль()
rent.type = тип

// Едем на машине, возвращаем ее и ничего не делаем
аренда.диск()
}
}

пусть компания = RentalCompany()
компания.rentCar()

По завершении функции rentCar() свойство rent по-прежнему ссылается на созданный объект Car.

Его счетчик сохранения больше нуля, поскольку экземпляр RentalCompany удерживает экземпляр Car, на который ссылается свойство аренды. Сильная ссылка сохраняет объект в памяти, поскольку он по-прежнему может использоваться классом RentalCompany.

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

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

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

Исходя из этого, вы можете случайно вызвать две распространенные проблемы с ARC:

  1. Слабая ссылка, когда вам нужно, чтобы она была сильной, поэтому экземпляр удаляется из памяти, когда он вам все еще понадобится позже.
  2. Ссылка, которая является сильной, когда вам нужно, чтобы она была слабой, и это вызывает цикл сохранения.

Особенно сложной проблемой является цикл сохранения. Это довольно просто: два объекта удерживаются друг на друге сильными ссылками, что означает, что ни один из них не может быть удален из памяти, поскольку оба их счетчика сохранения больше нуля. Это создает утечку памяти, чего мы и хотели избежать в первую очередь, управляя памятью и будучи «добропорядочными гражданами».

Вы можете решить проблему цикла сохранения, обозначив любую из сильных ссылок как слабую, но вам, конечно, придется учитывать и это изменение в своем коде. Циклы сохранения также влияют на дополнительные параметры, например [weak self] и бесхозный в замыканиях. Мы оставим это для другого урока!

Потрясающий! В этом руководстве вы узнали больше об управлении памятью с помощью автоматического подсчета ссылок (ARC) на iOS с помощью Swift.

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