Автоматическое управление ссылками
Моделируйте жизненный цикл объектов и их взаимосвязи.
Swift использует автоматическое управление ссылками (ARC), чтобы отслеживать и управлять использованием памяти в вашем приложении. В большинстве случаев это означает, что управление памятью «просто работает» в Swift, и вам не нужно думать о нём самостоятельно. ARC автоматически освобождает память, используемую экземплярами класса, когда эти экземпляры больше не нужны.
Однако в некоторых случаях ARC требует дополнительной информации о взаимосвязях между частями вашего кода, чтобы управлять памятью за вас. В этой главе описаны эти ситуации и показано, как вы можете позволить ARC управлять всей памятью вашего приложения. Использование ARC в Swift очень похоже на подход, описанный в Заметках о выпуске по переходу на ARC для использования ARC с Objective-C.
Управление ссылками применяется только к экземплярам классов. Структуры и перечисления являются значениями, а не ссылками, и не хранятся и не передаются по ссылке.
Как работает ARC
Каждый раз, когда вы создаёте новый экземпляр класса, ARC выделяет блок памяти для хранения информации об этом экземпляре. Эта память содержит информацию о типе экземпляра, а также значениях любых свойств, связанных с этим экземпляром.
Кроме того, когда экземпляр больше не нужен, ARC освобождает используемую им память, чтобы она могла быть использована для других целей. Это гарантирует, что экземпляры классов не занимают место в памяти, когда они больше не нужны.
Однако, если бы ARC удалял экземпляр, который всё ещё используется, доступ к свойствам этого экземпляра или вызов его методов стало бы невозможным. Действительно, если бы вы попытались получить доступ к экземпляру, ваше приложение, скорее всего, аварийно завершилось бы.
Чтобы убедиться, что экземпляры не исчезают, когда они всё ещё нужны, ARC отслеживает количество свойств, констант и переменных, которые в настоящее время ссылаются на каждый экземпляр класса. ARC не удалит экземпляр, пока существует хотя бы одна активная ссылка на него.
Для этого, всякий раз, когда вы присваиваете экземпляр класса свойству, константе или переменной, это свойство, константа или переменная создают сильную ссылку на экземпляр. Ссылка называется «сильной», потому что она крепко держится за экземпляр и не позволяет его удалить, пока эта сильная ссылка существует.
ARC в действии
Вот пример того, как работает автоматическое управление ссылками. Этот пример начинается с простого класса под названием Person, который определяет постоянное хранилище свойства под названием name:
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
} Класс Person имеет инициализатор, который устанавливает свойство экземпляра name и выводит сообщение, чтобы указать, что инициализация началась. Класс Person также имеет деинициализатор, который выводит сообщение, когда экземпляр класса удаляется.
Следующий фрагмент кода определяет три переменные типа Person?, которые используются для создания нескольких ссылок на новый экземпляр Person в последующих фрагментах кода. Поскольку эти переменные имеют необязательный тип (Person?, а не Person), они автоматически инициализируются значением nil и в настоящее время не ссылаются на экземпляр Person.
var reference1: Person? var reference2: Person? var reference3: Person?
Теперь вы можете создать новый экземпляр Person и присвоить его одной из этих трёх переменных:
reference1 = Person(name: "John Appleseed") // Prints "John Appleseed is being initialized"
Обратите внимание, что сообщение "John Appleseed is being initialized" выводится в тот момент, когда вы вызываете инициализатор класса Person. Это подтверждает, что инициализация произошла.
Поскольку новый экземпляр Person был присвоен переменной reference1, теперь существует сильная ссылка от reference1 на новый экземпляр Person. Поскольку существует хотя бы одна сильная ссылка, ARC гарантирует, что этот экземпляр Person хранится в памяти и не удаляется.
Если вы присвоите тот же экземпляр Person двум другим переменным, будут созданы две дополнительные сильные ссылки на этот экземпляр:
reference2 = reference1 reference3 = reference1
Теперь существует три сильных ссылки на этот единственный экземпляр Person.
Если вы разорвёте две из этих сильных ссылок (включая исходную ссылку), присвоив nil двум переменным, останется одна сильная ссылка, и экземпляр Person не будет удалён:
reference1 = nil reference2 = nil
ARC не удалит экземпляр Person, пока не будет разорвана третья и последняя сильная ссылка, в этот момент станет ясно, что вы больше не используете экземпляр Person:
reference3 = nil // Prints "John Appleseed is being deinitialized"
Циклы сильных ссылок между экземплярами классов
В приведённых выше примерах ARC может отслеживать количество ссылок на новый экземпляр Person, который вы создаёте, и удалять экземпляр Person, когда он больше не нужен.
Однако возможно написать код, в котором экземпляр класса никогда не достигнет состояния, в котором у него нет ни одной сильной ссылки. Это может произойти, если два экземпляра класса содержат сильную ссылку друг на друга, так что каждый экземпляр сохраняет другой живым. Это известно как цикл сильных ссылок.
Вы разрешаете циклы сильных ссылок, определив некоторые взаимосвязи между классами как слабые или неуправляемые ссылки вместо сильных ссылок. Этот процесс описан в <doc:AutomaticReferenceCounting#Resolving-Strong-Reference-Cycles-Between-Class-Instances>. Однако прежде чем узнать, как разрешить цикл сильной ссылки, полезно понять, как такой цикл образуется.
Вот пример того, как цикл сильной ссылки может быть создан случайно. В этом примере определены два класса под названием Person и Apartment, которые моделируют блок квартир и его жителей:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
} Каждый экземпляр Person имеет свойство name типа String и необязательное свойство apartment, которое изначально является nil. Свойство apartment необязательно, потому что у человека может не быть квартиры.
Аналогично, каждый экземпляр Apartment имеет свойство unit типа String и необязательное свойство tenant, которое изначально является nil. Свойство арендатора необязательно, потому что у квартиры может не быть жильца.
Оба этих класса также определяют деинициализатор, который выводит факт деинициализации экземпляра этого класса. Это позволяет вам увидеть, удаляются ли экземпляры Person и Apartment так, как ожидается.
Следующий фрагмент кода определяет две переменные необязательного типа под названием john и unit4A, которые будут установлены для определённого экземпляра Apartment и Person ниже. Обе эти переменные имеют начальное значение nil благодаря тому, что являются необязательными:
var john: Person? var unit4A: Apartment?
Теперь вы можете создать конкретный экземпляр Person и экземпляр Apartment и присвоить эти новые экземпляры переменным john и unit4A:
john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A")
Вот как выглядят сильные ссылки после создания и присвоения этих двух экземпляров. Переменная john теперь имеет сильную ссылку на новый экземпляр Person, а переменная unit4A имеет сильную ссылку на новый экземпляр Apartment:
Теперь вы можете связать два экземпляра, чтобы у человека была квартира, а у квартиры был жилец. Обратите внимание, что используется восклицательный знак (!), чтобы распаковать и получить доступ к экземплярам, хранящимся в необязательных переменных john и unit4A, чтобы можно было установить свойства этих экземпляров:
john!.apartment = unit4A unit4A!.tenant = john
Вот как выглядят сильные ссылки после того, как вы связали два экземпляра вместе:
К сожалению, связывание этих двух экземпляров создаёт цикл сильных ссылок между ними. Экземпляр Person теперь имеет сильную ссылку на экземпляр Apartment, а экземпляр Apartment имеет сильную ссылку на экземпляр Person. Поэтому, когда вы разорвете сильные ссылки, удерживаемые переменными john и unit4A, счётчики ссылок не падают до нуля, и экземпляры не удаляются ARC:
john = nil unit4A = nil
Обратите внимание, что ни один деинициализатор не был вызван, когда вы установили эти две переменные в nil. Цикл сильных ссылок предотвращает удаление экземпляров Person и Apartment, что приводит к утечке памяти в вашем приложении.
Вот как выглядят сильные ссылки после того, как вы установите переменные john и unit4A в nil:
Сильные ссылки между экземпляром Person и экземпляром Apartment сохраняются и не могут быть разорваны.
Разрешение циклов сильных ссылок между экземплярами классов
Swift предоставляет два способа разрешения циклов сильных ссылок при работе со свойствами ссылочного типа: слабые ссылки и неуправляемые ссылки.
Слабые и неуправляемые ссылки позволяют одному экземпляру в цикле ссылок ссылаться на другой экземпляр без удержания сильной связи. Таким образом, экземпляры могут ссылаться друг на друга без создания цикла сильных ссылок.
Используйте слабую ссылку, когда у другого экземпляра более короткий жизненный цикл — то есть, когда другой экземпляр может быть удалён первым. В примере Apartment выше уместно, чтобы у квартиры мог быть отсутствовать жилец в какой-то момент своего жизненного цикла, и поэтому слабая ссылка является подходящим способом разорвать цикл ссылки в этом случае. В отличие от этого, используйте неуправляемую ссылку, когда у другого экземпляра такой же или более длительный жизненный цикл.
Слабые ссылки
Слабая ссылка — это ссылка, которая не удерживает сильную связь с экземпляром, на который она ссылается, и, следовательно, не препятствует ARC от удаления ссылаемого экземпляра. Это поведение предотвращает превращение ссылки в цикл сильной ссылки. Вы указываете слабую ссылку, помещая ключевое слово weak перед объявлением свойства или переменной.
Поскольку слабая ссылка не удерживает сильную связь с экземпляром, на который она ссылается, возможно, что этот экземпляр будет удалён, в то время как слабая ссылка всё ещё на него ссылается. Поэтому ARC автоматически устанавливает слабую ссылку в nil, когда экземпляр, на который она ссылается, удаляется. И, поскольку слабые ссылки должны позволять изменять их значение на nil во время выполнения, они всегда объявляются как переменные, а не как константы, необязательного типа.
Вы можете проверить наличие значения в слабой ссылке, как и любого другого необязательного значения, и вы никогда не получите ссылку на недействительный экземпляр, который больше не существует.
Примечание: Наблюдатели свойств не вызываются, когда ARC устанавливает слабую ссылку на
nil.
Приведённый ниже пример идентичен примеру Person и Apartment из предыдущего раздела, с одним важным отличием. В этот раз свойство tenant типа Apartment объявлено как слабая ссылка:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
} Сильные ссылки из двух переменных (john и unit4A) и связи между двумя экземплярами создаются как и прежде:
var john: Person? var unit4A: Apartment? john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A") john!.apartment = unit4A unit4A!.tenant = john
Вот как выглядят ссылки сейчас, после того, как вы связали два экземпляра:
Экземпляр Person по-прежнему имеет сильную ссылку на экземпляр Apartment, но экземпляр Apartment теперь имеет слабую ссылку на экземпляр Person. Это означает, что когда вы прерываете сильную ссылку, удерживаемую переменной john, установив её значение в nil, больше нет сильных ссылок на экземпляр Person:
john = nil // Prints "John Appleseed is being deinitialized"
Поскольку больше нет сильных ссылок на экземпляр Person, он освобождается, и свойство tenant устанавливается в nil:
Единственной оставшейся сильной ссылкой на экземпляр Apartment является ссылка от переменной unit4A. Если вы прерываете эту сильную ссылку, больше нет сильных ссылок на экземпляр Apartment:
unit4A = nil // Prints "Apartment 4A is being deinitialized"
Поскольку больше нет сильных ссылок на экземпляр Apartment, он тоже освобождается:
Примечание: В системах, использующих сборку мусора, слабые указатели иногда используются для реализации простой системы кеширования, поскольку объекты без сильных ссылок освобождаются только при возникновении потребности в освобождении памяти. Однако при использовании ARC значения освобождаются сразу после удаления последней сильной ссылки, делая слабые ссылки непригодными для этой цели.
Ссылки без владения
Как и слабая ссылка, ссылка без владения не создаёт сильной связи с экземпляром, на который она указывает. В отличие от слабой ссылки, ссылка без владения используется, когда другой экземпляр имеет такой же или больший срок жизни. Вы указываете ссылку без владения, поместив ключевое слово unowned перед объявлением свойства или переменной.
В отличие от слабой ссылки, ссылка без владения ожидается, что она всегда будет иметь значение. В результате помечание значения как «без владения» не делает его необязательным, и ARC никогда не устанавливает значение ссылки без владения в nil.
Важно: Используйте ссылку без владения только тогда, когда вы уверены, что ссылка всегда указывает на экземпляр, который не был освобождён.
Если вы попытаетесь получить доступ к значению ссылки без владения после того, как этот экземпляр будет освобождён, вы получите ошибку во время выполнения.
В следующем примере определены два класса, Customer и CreditCard, которые моделируют клиента банка и возможную кредитную карту для этого клиента. Эти два класса хранят экземпляр другого класса в качестве свойства. Такое отношение может привести к созданию цикла сильных ссылок.
Взаимоотношения между Customer и CreditCard немного отличаются от отношений между Apartment и Person, показанных в примере со слабыми ссылками выше. В этой модели данных клиент может или не может иметь кредитную карту, но кредитная карта всегда будет связана с клиентом. Экземпляр CreditCard никогда не живёт дольше, чем Customer, на который он ссылается. Для представления этого класс Customer имеет необязательное свойство card, но класс CreditCard имеет ссылку без владения (и не является необязательной) customer.
Кроме того, новый экземпляр CreditCard может быть создан только путём передачи значения number и экземпляра класса customer в пользовательский инициализатор CreditCard. Это гарантирует, что экземпляр CreditCard всегда имеет связанный с ним экземпляр класса customer, когда экземпляр класса CreditCard создаётся.
Поскольку кредитная карта всегда будет иметь клиента, вы определяете свойство customer как ссылку без владения, чтобы избежать цикла сильных ссылок:
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
} Примечание: Свойство
numberклассаCreditCardопределено с типомUInt64, а неInt, чтобы гарантировать, что ёмкость свойстваnumberдостаточно велика для хранения 16-значного номера карты как на 32-битных, так и на 64-битных системах.
В следующем фрагменте кода определяется необязательная переменная Customer с именем john, которая будет использоваться для хранения ссылки на конкретного клиента. Эта переменная имеет начальное значение nil, так как является необязательной:
var john: Customer?
Теперь вы можете создать экземпляр Customer и использовать его для инициализации и присвоения нового экземпляра CreditCard в качестве свойства card этого клиента:
john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
Вот как выглядят ссылки сейчас, после связывания двух экземпляров:
Экземпляр Customer теперь имеет сильную ссылку на экземпляр CreditCard, а экземпляр CreditCard имеет ссылку без владения на экземпляр Customer.
Из-за ссылки без владения customer, когда вы прерываете сильную ссылку, удерживаемую переменной john, больше нет сильных ссылок на экземпляр Customer:
Поскольку больше нет сильных ссылок на экземпляр Customer, он освобождается. После этого больше нет сильных ссылок на экземпляр CreditCard, и он тоже освобождается:
john = nil // Prints "John Appleseed is being deinitialized" // Prints "Card #1234567890123456 is being deinitialized"
Последний фрагмент кода демонстрирует, что деинициализаторы для экземпляра Customer и экземпляра CreditCard оба печатают свои сообщения «деинициализирован» после того, как переменная john установлена в значение nil.
Примечание: Примеры выше демонстрируют использование безопасных ссылок без владения. Swift также предоставляет небезопасные ссылки без владения для случаев, когда вам нужно отключить проверки безопасности во время выполнения — например, для повышения производительности. Как и при всех небезопасных операциях, вы берёте на себя ответственность за проверку кода на безопасность.
Вы указываете небезопасную ссылку без владения, написав
unowned(unsafe). Если вы попытаетесь получить доступ к небезопасной ссылке без владения после того, как экземпляр, на который она указывает, будет освобождён, ваша программа попытается получить доступ к области памяти, где этот экземпляр был, что является небезопасной операцией.
Необязательные ссылки без владения
Вы можете пометить необязательную ссылку на класс как ссылку без владения. С точки зрения модели владения ARC, необязательная ссылка без владения и слабая ссылка могут использоваться в тех же контекстах. Разница заключается в том, что при использовании необязательной ссылки без владения вы несете ответственность за то, чтобы она всегда указывала на допустимый объект или была установлена в nil.
Вот пример, который отслеживает курсы, предлагаемые определённым отделом в школе:
class Department {
var name: String
var courses: [Course]
init(name: String) {
self.name = name
self.courses = []
}
}
class Course {
var name: String
unowned var department: Department
unowned var nextCourse: Course?
init(name: String, in department: Department) {
self.name = name
self.department = department
self.nextCourse = nil
}
} Department поддерживает сильную ссылку на каждый курс, предлагаемый отделом. В модели владения ARC отдел владеет своими курсами. Course имеет две ссылки без владения, одну на отдел и одну на следующий курс, который должен пройти студент; курс не владеет ни одним из этих объектов. Каждый курс принадлежит какому-то отделу, поэтому свойство department не является необязательным. Однако, поскольку некоторые курсы не имеют рекомендуемого последующего курса, свойство nextCourse является необязательным.
Вот пример использования этих классов:
let department = Department(name: "Horticulture") let intro = Course(name: "Survey of Plants", in: department) let intermediate = Course(name: "Growing Common Herbs", in: department) let advanced = Course(name: "Caring for Tropical Plants", in: department) intro.nextCourse = intermediate intermediate.nextCourse = advanced department.courses = [intro, intermediate, advanced]
Код выше создаёт отдел и три его курса. Вводный и промежуточный курсы оба имеют предложенный следующий курс, сохранённый в их свойстве nextCourse, которое поддерживает необязательную ссылку без владения на курс, который студент должен пройти после этого.
Необязательная ссылка без владения не поддерживает сильную ссылку на экземпляр класса, на который она указывает, и поэтому она не предотвращает освобождение экземпляра ARC. Она ведёт себя так же, как ссылка без владения в ARC, за исключением того, что необязательная ссылка без владения может быть nil.
Как и ссылки без владения, вы несете ответственность за обеспечение того, чтобы nextCourse всегда указывала на курс, который не был освобождён. В этом случае, например, при удалении курса из department.courses вам также необходимо удалить все ссылки на него, которые могут быть у других курсов.
Примечание: Базовый тип необязательного значения —
Optional, который представляет собой перечисление в стандартной библиотеке Swift. Однако необязательные значения являются исключением из правила, что типы значений не могут быть помеченыunowned.Необязательный тип, который обертывает класс, не использует подсчёт ссылок, поэтому вам не нужно поддерживать сильную ссылку на необязательный тип.
Ссылки без владения и неявно распакованные необязательные свойства
Примеры слабых и ссылок без владения выше охватывают два из наиболее распространённых сценариев, в которых необходимо разорвать цикл сильных ссылок.
Пример с Person и Apartment показывает ситуацию, когда два свойства, оба из которых могут быть nil, имеют возможность вызвать цикл сильных ссылок. Этот сценарий лучше всего решается с помощью слабой ссылки.
Пример с Customer и CreditCard показывает ситуацию, когда одно свойство, которое может быть nil, и другое свойство, которое не может быть nil, имеют возможность вызвать цикл сильных ссылок. Этот сценарий лучше всего решается с помощью ссылки без владения.
Однако существует третий сценарий, в котором оба свойства должны всегда иметь значение, и ни одно из свойств не должно быть nil после завершения инициализации. В этом случае полезно объединить свойство без владения в одном классе с неявно распакованным необязательным свойством в другом классе.
Это позволяет получить прямой доступ к обоим свойствам (без распаковки необязательных значений) после завершения инициализации, при этом по-прежнему избегая цикла ссылок. Этот раздел покажет, как настроить такие отношения.
В приведенном ниже примере определены два класса, Country и City, каждый из которых хранит экземпляр другого класса в качестве свойства. В этой модели данных каждая страна должна всегда иметь столицу, а каждый город должен всегда принадлежать стране. Для представления этого класс Country имеет свойство capitalCity, а класс City имеет свойство country:
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
} Для настройки взаимозависимости между двумя классами инициализатор для City принимает экземпляр класса Country и хранит этот экземпляр в своем свойстве country.
Инициализатор для City вызывается изнутри инициализатора для Country. Однако инициализатор для Country не может передать self в инициализатор для City до тех пор, пока новый экземпляр Country не будет полностью инициализирован, как описано в <doc:Initialization#Two-Phase-Initialization>.
Чтобы справиться с этим требованием, вы объявляете свойство capitalCity класса Country как свойство с неявным разворачиванием необязательного значения, обозначенное восклицательным знаком в конце аннотации типа (City!). Это означает, что свойство capitalCity имеет значение по умолчанию nil, как и любое другое необязательное значение, но к нему можно получить доступ без необходимости разворачивать его значение, как описано в <doc:TheBasics#Implicitly-Unwrapped-Optionals>.
Поскольку у capitalCity есть значение по умолчанию nil, новый экземпляр Country считается полностью инициализированным, как только экземпляр Country устанавливает свойство name в своем инициализаторе. Это означает, что инициализатор для Country может начать ссылаться и передавать вокруг неявное свойство self, как только свойство name будет установлено. Таким образом, инициализатор для Country может передать self в качестве одного из параметров для инициализатора City, когда инициализатор для Country устанавливает свое собственное свойство capitalCity.
Все это означает, что вы можете создать экземпляры Country и City в одном операторе, не создавая цикла сильной ссылки, и к свойству capitalCity можно получить доступ напрямую, без необходимости использовать восклицательный знак для разворачивания его необязательного значения:
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa" В приведенном выше примере использование неявного разворачивания необязательного значения означает, что все требования к двухфазному инициализатору класса выполнены. Свойство capitalCity можно использовать и получить доступ к нему как к значению без необязательности после завершения инициализации, одновременно избегая цикла сильной ссылки.
Циклы сильной ссылки для замыканий
Вы видели выше, как может быть создан цикл сильной ссылки, когда два свойства экземпляра класса содержат сильную ссылку друг на друга. Вы также видели, как использовать слабые и неуправляемые ссылки, чтобы разорвать эти циклы сильной ссылки.
Цикл сильной ссылки также может возникнуть, если вы присваиваете замыкание свойству экземпляра класса, а тело этого замыкания захватывает экземпляр. Это захват может произойти, потому что тело замыкания обращается к свойству экземпляра, например, self.someProperty, или потому, что замыкание вызывает метод на экземпляре, например, self.someMethod(). В любом случае эти обращения заставляют замыкание «захватить» self, создавая цикл сильной ссылки.
Этот цикл сильной ссылки возникает потому, что замыкания, как и классы, являются типами ссылок. Когда вы присваиваете замыкание свойству, вы присваиваете ссылку на это замыкание. По сути, это та же проблема, что и выше — две сильные ссылки поддерживают друг друга в живом состоянии. Однако вместо двух экземпляров класса на этот раз это экземпляр класса и замыкание, поддерживающие друг друга в живом состоянии.
Swift предлагает элегантное решение этой проблемы, известное как список захватов замыкания. Однако прежде чем вы узнаете, как разорвать цикл сильной ссылки с помощью списка захватов замыкания, полезно понять, как может быть вызван такой цикл.
В приведенном ниже примере показано, как можно создать цикл сильной ссылки при использовании замыкания, которое ссылается на self. В этом примере определен класс под названием HTMLElement, который предоставляет простую модель для отдельного элемента в документе HTML:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
} Класс HTMLElement определяет свойство name, которое указывает имя элемента, например, "h1" для заголовка, "p" для абзаца или "br" для разрыва строки. Класс HTMLElement также определяет необязательное свойство text, которое можно установить в строку, представляющую текст, который должен быть отображен в этом элементе HTML.
В дополнение к этим двум простым свойствам класс HTMLElement определяет ленивое свойство asHTML. Это свойство ссылается на замыкание, которое объединяет name и text в фрагмент строки HTML. Свойство asHTML имеет тип () -> String, или «функция, которая не принимает параметров и возвращает значение типа String».
По умолчанию свойство asHTML получает замыкание, которое возвращает строковое представление тега HTML. Этот тег содержит необязательное значение text, если оно существует, или нет текстового содержимого, если text не существует. Для элемента абзаца замыкание вернет "<p>some text</p>" или "<p />" в зависимости от того, равно ли свойство text "some text" или nil.
Свойство asHTML названо и используется примерно как метод экземпляра. Однако, поскольку asHTML является свойством замыкания, а не методом экземпляра, вы можете заменить значение по умолчанию свойства asHTML пользовательским замыканием, если хотите изменить отображение HTML для определенного элемента HTML.
Например, свойство asHTML можно установить на замыкание по умолчанию, которое будет возвращать некоторый текст, если свойство text равно nil, чтобы предотвратить возвращение пустого тега HTML:
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// Prints "<h1>some default text</h1>" Примечание: Свойство
asHTMLобъявлено как ленивое свойство, потому что оно нужно только в том случае, если и когда элемент фактически должен быть преобразован в строковое значение для некоторого целевого вывода HTML. Тот факт, чтоasHTMLявляется ленивым свойством, означает, что вы можете ссылаться наselfвнутри замыкания по умолчанию, потому что к ленивому свойству не будет обращаться до тех пор, пока инициализация не будет завершена, иselfне будет известно.
Класс HTMLElement предоставляет единственный инициализатор, который принимает аргумент name и (при необходимости) аргумент text для инициализации нового элемента. Класс также определяет деинициализатор, который выводит сообщение, чтобы показать, когда экземпляр класса HTMLElement удаляется.
Вот как вы используете класс HTMLElement для создания и вывода нового экземпляра:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) // Prints "<p>hello, world</p>"
Примечание: Переменная
paragraphвыше определена как необязательныйHTMLElement, чтобы ее можно было установить наnilниже, чтобы продемонстрировать наличие цикла сильной ссылки.
К сожалению, класс HTMLElement, как написан выше, создает цикл сильной ссылки между экземпляром класса HTMLElement и замыканием, используемым для его значения по умолчанию asHTML. Вот как выглядит цикл:
Свойство asHTML экземпляра содержит сильную ссылку на его замыкание. Однако, поскольку замыкание ссылается на self в своем теле (как способ ссылки на self.name и self.text), замыкание захватывает self, что означает, что оно содержит сильную ссылку обратно на экземпляр HTMLElement. Между ними создается цикл сильной ссылки. (Дополнительную информацию о захвате значений в замыкании см. в <doc:Closures#Capturing-Values>.)
Примечание: Несмотря на то, что замыкание многократно ссылается на
self, оно захватывает только одну сильную ссылку на экземпляр классаHTMLElement.
Если вы установите переменную paragraph в nil и прервете ее сильную ссылку на экземпляр HTMLElement, цикл сильной ссылки предотвратит удаление как экземпляра HTMLElement, так и его замыкания:
paragraph = nil
Обратите внимание, что сообщение в деинициализаторе HTMLElement не выводится, что показывает, что экземпляр HTMLElement не удаляется.
Разрешение циклов сильной ссылки для замыканий
Вы разрешаете цикл сильной ссылки между замыканием и экземпляром класса, определив список захватов как часть определения замыкания. Список захватов определяет правила использования при захвате одного или нескольких типов ссылок в теле замыкания. Как и с циклами сильной ссылки между двумя экземплярами класса, вы объявляете каждую захваченную ссылку как слабую или неуправляемую ссылку, а не сильную ссылку. Правильный выбор между слабой и неуправляемой ссылкой зависит от взаимоотношений между различными частями вашего кода.
Примечание: Swift требует от вас писать
self.somePropertyилиself.someMethod()(а не простоsomePropertyилиsomeMethod()) всякий раз, когда вы ссылаетесь на членselfвнутри замыкания. Это помогает вам помнить, что можно случайно захватитьself.
Определение списка захватов
Каждый элемент в списке захватов — это пара ключевого слова weak или unowned со ссылкой на экземпляр класса (например, self) или переменную, инициализированную каким-либо значением (например, delegate = self.delegate). Эти пары записываются внутри пары квадратных скобок, разделенных запятыми.
Разместите список захватов перед списком параметров замыкания и типом возвращаемого значения, если они заданы:
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// closure body goes here
} Если для замыкания не указан список параметров или тип возвращаемого значения, потому что их можно определить из контекста, поместите список захватов в самое начало замыкания, за которым следует ключевое слово in:
lazy var someClosure = {
[unowned self, weak delegate = self.delegate] in
// closure body goes here
} Слабые и неуправляемые ссылки
Определите захват в замыкании как неуправляемую ссылку, когда замыкание и экземпляр, который оно захватывает, всегда будут ссылаться друг на друга и всегда будут удалены одновременно.
И наоборот, определите захват как слабую ссылку, когда захваченная ссылка может стать nil в какой-то момент будущего. Слабые ссылки всегда являются необязательного типа и автоматически становятся nil, когда экземпляр, на который они ссылаются, удаляется. Это позволяет проверять их существование в теле замыкания.
Примечание: если захваченная ссылка никогда не станет
nil, её всегда следует захватывать как несвязанную ссылку, а не слабую ссылку.
Несвязанная ссылка — это подходящий метод захвата для решения цикла сильной ссылки в примере HTMLElement из <doc:AutomaticReferenceCounting#Strong-Reference-Cycles-for-Closures> выше. Вот как вы пишете класс HTMLElement, чтобы избежать цикла:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
} Это реализация HTMLElement идентична предыдущей реализации, за исключением добавления списка захвата в замыкание asHTML. В этом случае список захвата — это [unowned self], что означает «захватить self как несвязанную ссылку, а не сильную ссылку».
Вы можете создать и распечатать экземпляр HTMLElement, как и раньше:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) // Prints "<p>hello, world</p>"
Вот как выглядят ссылки со списком захвата:
На этот раз захват self замыканием — это несвязанная ссылка и не удерживает сильную связь с экземпляром HTMLElement, который она захватила. Если вы установите сильную ссылку из переменной paragraph на nil, экземпляр HTMLElement удаляется, как видно из вывода сообщения деинициализатора в примере ниже:
paragraph = nil // Prints "p is being deinitialized"
Для получения дополнительной информации о списках захвата см. <doc:Expressions#Capture-Lists>.
This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception