Tehnografi.com - Технологические новости, обзоры и советы
[adinserter block="67"]

Слабые и сильные ссылки в Swift

Следующая статья поможет вам: Слабые и сильные ссылки в Swift

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

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

Вот во что мы вникнем:

  • Что такое сильная ссылка и зачем она нам нужна?
  • Слабые и сильные ссылки и как они влияют на ваш код
  • Как избежать сильных циклов ссылок между объектами и замыканиями
  • Сильные, слабые и бесхозные в замыканиях
  • Когда использовать [weak self] и [unowned self]

Сильные ссылки увеличивают количество сохраняемых экземпляров, на которые они ссылаются (на 1). Это предотвращает удаление объекта из памяти автоматическим подсчетом ссылок (ARC), пока объект используется.

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

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

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

Note: ARC влияет только на ссылочные типы, например на классы. Это не влияет на типы значений, такие как структуры. Типы значений копируются при передаче или ссылке на них, поэтому за пределами локальной области в памяти нечего сохранять.

Давайте рассмотрим пример сильной ссылки. Проверь это:

класс Человек {
вар друг: Человек?
пусть имя:Строка

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

деинит {
print(“\(имя) деинициализировано”)
}
}

В приведенном выше коде мы создали класс Person. У него есть инициализатор, принимающий имя типа String. И init, и deinit сообщат нам, когда экземпляр класса Person инициализируется и деинициализируется, а затем освобождается и удаляется из памяти.

Класс Person также имеет дружественное свойство типа Person. Это свойство создаст строгую ссылку на любой назначенный ему объект.

var bob = Человек(имя: «Боб»)
bob.friend = Человек(имя: «Алиса»)

Что тут происходит? Все просто: мы создаем экземпляр Person и присваиваем его переменной bob. Затем мы создаем еще один экземпляр Person и присваиваем его bob.friend, т. е. свойству друга объекта bob.

Это дает нам следующий результат:

Боб инициализирован
Алиса инициализируется

На данный момент в этом примере у нас есть 2 сильных ссылки.

  1. Локальная переменная bob строго ссылается на экземпляр Person с именем Bob.
  2. Свойство друга строго ссылается на экземпляр Person с именем Алиса.

Это означает, что Алиса будет освобождена только тогда, когда будет освобожден Боб, потому что Боб сильно удерживает Алису. Пока все хорошо, и к концу кода оба экземпляра Person, назначенные bob и bob.friend, освобождаются, поскольку код завершается и завершается.

В любой момент мы можем явно освободить Алису следующим образом:

боб.друг = ноль
// Вывод: Алиса деинициализируется

Если бы bob был необязательным, то есть Person?, мы могли бы увидеть, как установка bob в ноль освобождает и Боба, и Алису. Проверь это:

вар боб:Человек? = Человек(имя: «Боб»)
bob?.friend = Человек(имя: «Алиса»)
боб = ноль
// Выходы: Боб деинициализируется, Алиса деинициализируется

Зная, как работает ARC, вы можете утверждать следующие истины о сильных ссылках:

  • В Swift по умолчанию используется сильная ссылка. Когда вы создаете переменную, константу или свойство для ссылочного типа, по умолчанию создается сильная ссылка. Это также верно для передачи ссылок в функции или замыкания.
  • Экземпляр освобождается только тогда, когда его счетчик сохранения равен нулю; когда никакие другие объекты не удерживают его. Сильная ссылка увеличивает счетчик сохранения на единицу, поэтому сильную ссылку необходимо разорвать, прежде чем можно будет освободить экземпляр, на который она ссылается.
  • Оба принципа вы видели в последнем примере кода. Боб сильно держал Алису, поэтому Алиса была освобождена только после того, как это сделал Боб.

Семантика важна в общении, но жаргон иногда мешает хорошему примеру. Вы можете рассматривать ссылку как «связь» между объектом и именем переменной, например var name = object. Эта ссылка может быть сильной (или слабой). Это называется ссылкой, потому что несколько переменных, констант и свойств могут сохранять «ссылку» на один и тот же объект в памяти. Чтобы узнать, когда удалить этот объект, необходимо отслеживать, кто его держит (т. е. ARC). Когда никто не держится, предмет убирают, чтобы освободить место.

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

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

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

Вернемся к слабости. Проверь это:

класс Человек {
слабый вар друг: Человек?
пусть имя:Строка

}

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

вар боб:Человек? = Человек(имя: «Боб»)
bob?.friend = Человек(имя: «Алиса»)

Приведенный выше код приводит к следующему выводу:

Боб инициализирован
Алиса инициализируется
Алиса деинициализируется

Что тут происходит? Похоже, Алиса после инициализации сразу деинициализируется. В этом есть смысл! Боб не удерживает Алису – из-за слабой ссылки – поэтому Алиса удаляется из памяти, поскольку счетчик сохранения экземпляра остается нулевым.

Однако при практической разработке для iOS вы бы этого не сделали, точно так же, как вы бы не написали код для Боба и Алисы. Когда вы запустите приведенный выше код в Xcode, вы даже получите предупреждение: экземпляр будет немедленно освобожден, поскольку свойствоFriend является слабым.

На основании примера мы можем сделать 2 утверждения о слабых ссылках:

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

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

Когда слабый используется в практической разработке iOS?

  1. Объекты. Это происходит, когда вы случайно создаете связь между двумя экземплярами, например представлением и связанным с ним родительским контроллером представления. Существует ссылка, идущая «вниз» от VC к представлению и «вверх» от представления к VC. Например, чтобы вы могли изменить или вызвать контроллер представления изнутри представления. Это сомнительный дизайнерский выбор, но такое случается.
  2. Делегаты. Свойство делегата является слабым по своей конструкции, поскольку в противном случае оно вызвало бы сильный цикл ссылок. Свойство dataSource контроллера табличного представления стало слабым. Если бы он был сильным, контроллер табличного представления и источник данных сильно держались бы друг за друга и вызывали бы утечку памяти.
  3. Замыкания. Замыкание может захватывать переменные, свойства и т. д. из окружающей его области. Когда замыкание переживает область, в которой оно было объявлено, оно может сильно удерживать объекты из этой области. Это может вызвать сильный опорный цикл.

Наиболее распространенными сценариями слабости в повседневной разработке iOS являются объекты, которые ссылаются друг на друга, например двусторонняя связь между объектами или контроллерами представлений, а также плохой захват в замыканиях. Давайте обсудим это дальше!

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

Замыкания — это автономные блоки кода, которые можно передавать. Вы можете рассматривать их как функции, которые можно назначить переменным и передать в свой код, чтобы выполнить их позже. Современные Swift и SwiftUI в значительной степени полагаются на использование замыканий, потому что они очень мощные.

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

Вот пример:

пусть imageView:UIImageView =

let Task = session.dataTask(with: «https://imgur.com/cats.png», завершениеHandler: {данные, ответ, ошибка в

imageView.image = UIImage(данные: данные)
})

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

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

Что здесь «схвачено»? Ссылка на экземпляр UIImageView, назначенная imageView, фиксируется замыканием. Это означает, что экземпляр UIImageView доступен для использования после того, как область, окружающая замыкание (т. е. функция), исчезнет.

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

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

Сильные, слабые, бесхозные списки и списки захвата

В отличие от свойств, замыкания имеют свой собственный синтаксис, обозначающий захваченное значение как слабое, называемый списком захвата. Вот пример:

пусть imageView:UIImageView =

пусть задача = dataTask (с: , завершениеHandler: { [weak imageView] данные, ответ, ошибка в

imageView?.image = UIImage(данные: данные)
})

В приведенном выше коде мы используем список захвата [weak imageView] чтобы указать, что ссылка на imageView должна быть слабой. Как и раньше, здесь по умолчанию используется сильный.

Всего у вас есть 3 варианта:

  1. Сильный: Это значение по умолчанию, и вы не используете список захвата. Просто используйте объект в замыкании, и все в порядке. Это работает в большинстве сценариев!
  2. Слабый: Вы используете слабую ссылку для создания слабой ссылки на объект. Это автоматически сделает этот экземпляр необязательным внутри замыкания.
  3. Без владельца: То же, что и слабый, за исключением того, что указанный экземпляр не является необязательным. Вы используете это, когда уверены, что экземпляр не может стать нулевым.

Когда вы используете сильные, слабые или бесхозные ссылки с замыканиями? Давайте сначала установим базовый уровень и рассмотрим обычное использование списков захвата.

класс ImageVC: UIViewController
{
вар imageView =

функция getImage()
{
dataTask(with: ,completeHandler: {данные, ответ, ошибка в
imageView?.image = UIImage(данные: данные)
})
}
}

В приведенном выше коде мы создали контроллер представления. В какой-то момент вызывается его функция getImage(). Эта функция выполняет асинхронный HTTP-запрос на загрузку изображения, которое впоследствии отображается в представлении изображения.

На данный момент технически в приведенном выше коде нет ничего плохого. Но что, если, например, контроллер представления является частью контроллера навигации?

Note: Как и раньше, технически вам нужен только слабый сигнал, чтобы избежать сильных опорных циклов. Не используйте его с самого начала; только тогда, когда вам это нужно, потому что сильный — это (разумное) значение по умолчанию.

Ссылочные циклы в замыканиях

В какой-то момент HTTP-запрос находится на рассмотрении, и пользователь уходит от контроллера представления. Контроллер представления обычно удаляется из стека представлений и освобождается. Однако, поскольку мы создали строгую ссылку на imageView, контроллер представления не может быть освобожден, пока сильная ссылка не будет удалена.

Из-за гипотетической ошибки где-то в функции dataTask() замыкание не освобождается, а остается присвоенным какому-то сильному свойству. Это создает сильный цикл ссылок между замыканием и контроллером представления. Ни один из них не может быть освобожден, потому что они оба сильно держатся друг за друга.

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

Слабый против незарегистрированного

Зачем же тогда бесхозный? Существует много путаницы в отношении слабых и бесхозных, особенно с [weak self] и [unowned self].

Прежде чем мы углубимся в это, важно отметить, что вы, как правило, избегаете полностью отмечать себя как слабого или бесхозного. Например, когда вы используете imageView в замыкании, используйте imageView только в списке захвата. Несмотря на то, что с помощью imageView вы неявно используете self, используя [weak imageView] вместо [weak self] делает то, что вы снимаете, кристально ясным.

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

Цена unowned заключается в том, что вам нужно быть уверенным, что указанный экземпляр не станет нулевым. Самый простой способ сделать это — убедиться, что экземпляр, на который ссылаются, переживет закрытие. Другими словами, если вам необходимо использовать [unowned self] в контроллере представления убедитесь, что контроллер представления существует до или после освобождения замыкания.

Полезный способ запомнить разницу между слабым и бесхозным заключается в значении этих слов. «Слабый» подразумевает ссылку, т.е. объекты со связью между собой, живущие независимо. «Unowned» означает, что замыкание не владеет экземпляром, на который оно ссылается. Если замыканию не принадлежит то, что оно использует, ему лучше завершить свои дела до того, как этот объект перестанет существовать.

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

Вот краткое изложение принципов, которые мы обсуждали:

  • В Swift сильная ссылка используется по умолчанию для переменных, свойств, констант, передачи в функции (в зависимости от того, что вы с ними делаете) и для замыканий.
  • При использовании ARC экземпляр освобождается только тогда, когда его счетчик сохранения равен нулю. Сильная ссылка увеличивает счетчик сохранений на 1, слабая — нет.
  • Вы можете пометить ссылку как слабую с помощью ключевого слова слабый. Это сделает экземпляр необязательным. Слабая ссылка не влияет на счетчик сохранения экземпляра.
  • Обычно вы сталкиваетесь с сильными ссылками между экземплярами, например. со свойствами делегата или двусторонними связями между объектами или внутри замыканий. Замыкание захватывает значения из окружающей его области и создает на них сильные ссылки.
  • Вы можете управлять этими ссылками с помощью списка захвата, например [weak imageView]. Вы можете выбирать между сильной, слабой или бесхозной ссылкой. Слабая ссылка становится необязательной, а ссылка, не принадлежащая владельцу, — нет. Вы используете unowned, когда экземпляр, на который ссылаются, переживет закрытие.