Привет свифтийцы

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

Те, кто знает, давайте погрузимся в это.

Есть три связанных сценария, когда два объекта ссылаются друг на друга, что приводит к циклу сильных ссылок. Обсудим их по очереди.

# Первый сценарий

Когда оба свойства могут стать nil в любой момент ссылочного цикла, как мы видели в примере в предыдущем сообщении, то есть Person и Apartment.

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

Что такое слабая ссылка?

Слабая ссылка - это ссылка, которая не сильно удерживает экземпляр, на который она ссылается, и поэтому не останавливает ARC от удаления указанного экземпляра.

Мы можем представить слабую ссылку, поместив ключевое слово weak перед свойством.

Поскольку это слабая ссылка, поэтому всякий раз, когда экземпляр, на который она ссылается, освобождается, ARC автоматически устанавливает для этой слабой ссылки значение nil. Таким образом, слабые ссылки должны быть необязательными, а не необязательными.

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

Давайте рассмотрим это на примере:

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

Как мы знаем, Appartment может не иметь арендатора в какой-то более поздний момент времени, поэтому мы можем сказать, что арендатор имеет более короткий промежуток времени, чем экземпляр Appartment. Итак, мы сделаем tenant слабой ссылкой на экземпляр класса Person.

class Person {
 var apartment: Apartment?
 deinit { print("Person is being deinitialized") }
}
class Apartment {
 weak var tenant: Person?
 deinit { print("Apartment is being deinitialized") }
}

# Второй сценарий

Когда одно из свойств может быть равно нулю, а другое не может быть nil.

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

Что такое бесхозная ссылка?

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

Это означает, что экземпляр, на который имеется ссылка как Unowned, всегда будет иметь значение, поэтому он всегда объявляется как необязательный. ARC никогда не устанавливает для бесхозной ссылки значение nil, даже если объект, на который она ссылается, освобожден. Таким образом, вы должны убедиться, что незарегистрированная ссылка не должна быть освобождена, иначе, если вы попытаетесь получить доступ, это приведет к ошибке времени выполнения.

Пример:

Рассмотрим на примере клиентов и BankAccount. Здесь отношения между клиентом и BankAccount немного отличаются от примера с человеком и квартирой.

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

class Customer {
 let name: String
 var account: BankAccount?
 init(name: String) {
  self.name = name
 }
 deinit { print("\(name) is being deinitialized") }
}
class BankAccount {
 let number: UInt64
 let customer: Customer
 init(number: UInt64, customer: Customer) {
  self.number = number
  self.customer = customer
 }
 deinit { 
  print("BankAccount with account number \(number) is being    deinitialized") 
 }
}

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

var harry: Customer?
harry = Customer(name: "Harry")
harry?.account = BankAccount(number: 1323243214214, customer: harry!)
harry = nil

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

Давайте посмотрим с помощью диаграммы.

Давайте разорвем этот цикл сильных ссылок с помощью ссылки Unowned. Теперь код будет выглядеть так.

class Customer {
 let name: String
 var account: BankAccount?
 init(name: String) {
   self.name = name
 }
 deinit { print("\(name) is being deinitialized") }
}
class BankAccount {
 let number: UInt64
 unowned let customer: Customer
 init(number: UInt64, customer: Customer) {
   self.number = number
   self.customer = customer
 }
 deinit { 
   print("BankAccount with account number \(number) is being  deinitialized") 
 }
}

При установке harryto nil нет строгой ссылки на объект Customer. Следовательно, он освобожден. Когда он освобождается, объект BankAccount также освобождается, поскольку на него также нет сильной ссылки. Следовательно, все ссылки между then сломаны и нет утечки памяти.

# Третий сценарий

Когда оба свойства всегда должны иметь значение, и ни одно свойство никогда не должно быть nil после завершения инициализации.

В этом случае мы будем использовать комбинацию свойства Неявно развернутый необязательный для одного класса и свойства Без владельца для другого класса.

Здесь мы рассмотрим пример, чтобы лучше понять это:

class Country {
  let name: String
  var capitalCity: City!
  init(name: String, capitalName: String) {
    self.name = name
    self.capitalCity = City(name: capitalName, country: self)
  }
  deinit {
    print("Country deinitialized")
  }
}
class City {
  let name: String
  unowned let country: Country
  init(name: String, country: Country) {
    self.name = name
    self.country = country
  }
  deinit {
    print("City deinitialized")
  }
}

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

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

Удачного кодирования :)

Если вам понравился пост, нажмите кнопку ❤️ и распространите слово.

Оставьте комментарий ниже, чтобы оставить отзыв и помочь мне улучшить содержание.