Swift использует автоматический подсчет ссылок (ARC) для отслеживания и управления использованием памяти вашего приложения. В большинстве случаев это означает, что управление памятью «просто работает» в Swift, и вам не нужно думать об управлении памятью самостоятельно. ARC автоматически освобождает память, используемую экземплярами класса, когда эти экземпляры больше не нужны.
Однако в некоторых случаях ARC требуется больше информации об отношениях между частями вашего кода, чтобы управлять памятью для вас. В этой главе описываются эти ситуации и показано, как вы разрешаете ARC управлять всей памятью вашего приложения.
Подсчет ссылок применяется только к экземплярам классов. Структуры и перечисления являются типами значений, а не ссылочными типами, и не хранятся и не передаются по ссылке.
Как работает 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
экземпляр, когда он больше не нужен.
Тем не менее, можно написать код, в котором экземпляр класса никогда не попадет в точку, где у него нулевые сильные ссылки. Это может произойти, если два экземпляра класса содержат сильную ссылку друг на друга, так что каждый экземпляр поддерживает другой. Это известно как сильный справочный цикл .
Вы разрешаете циклы сильных ссылок, определяя некоторые отношения между классами как слабые или неподтвержденные ссылки, а не как сильные ссылки. Однако, прежде чем вы узнаете, как разрешить сильный ссылочный цикл, полезно понять, как возникает такой цикл.
Вот пример того, как сильный эталонный цикл может быть создан случайно. Этот пример определяет два класса, называемых 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
and 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
переменных, то число ссылок не падает до нуля, и экземпляры не освобождаться от АРК:
john = nil
unit4A = nil
Обратите внимание, что ни один деинициализатор не был вызван при установке этих двух переменных в nil
. Сильный ссылочный цикл предотвращает освобождение экземпляров Person
и Apartment
экземпляров, что приводит к утечке памяти в вашем приложении. Строгие ссылки между Person
экземпляром и Apartment
экземпляром остаются и не могут быть нарушены.
Устранение сильных ссылочных циклов между экземплярами классов
Swift предоставляет два способа разрешения циклов сильных ссылок при работе со свойствами типа класса: слабые ссылки и неизвестные ссылки.
Слабые и неподтвержденные ссылки позволяют одному экземпляру в ссылочном цикле ссылаться на другой, не удерживая его крепко. Затем экземпляры могут ссылаться друг на друга, не создавая сильный ссылочный цикл.
Используйте слабую ссылку, когда другой экземпляр имеет более короткое время жизни, то есть когда другой экземпляр может быть сначала освобожден. В Apartment
приведенном выше примере целесообразно, чтобы квартира не имела арендатора в какой-то момент его жизненного цикла, поэтому слабая ссылка является подходящим способом разорвать ссылочный цикл в этом случае. Напротив, используйте неопознанную ссылку, когда другой экземпляр имеет такое же или большее время жизни.
Слабые ссылки
Слабая ссылка является ссылкой , что не держит сильные позиции на экземпляре она относится, и поэтому не мешает ARC распоряжаться ссылочного экземпляра. Такое поведение препятствует тому, чтобы ссылка стала частью сильного ссылочного цикла. Вы указываете слабую ссылку, помещая weak
ключевое слово перед объявлением свойства или переменной.
Поскольку слабая ссылка не удерживает сильную привязку к экземпляру, на который она ссылается, этот экземпляр может быть освобожден, пока слабая ссылка все еще ссылается на него. Следовательно, ARC автоматически устанавливает слабую ссылку, nil
когда экземпляр, на который он ссылается, освобождается. И поскольку слабые ссылки должны позволять изменять их значение nil
во время выполнения, они всегда объявляются как переменные, а не как константы необязательного типа.
Вы можете проверить наличие значения в слабой ссылке, как и любое другое необязательное значение, и у вас никогда не будет ссылки на недопустимый экземпляр, который больше не существует.
ЗАМЕТКА
Наблюдатели свойств не вызываются, когда ARC устанавливает слабую ссылку на
nil
.
Приведенное ниже пример идентичен Person
и Apartment
пример сверху, с одним важным различием. На этот раз свойство Apartment
типа tenant
объявлено как слабая ссылка:
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"
Единственная остающаяся сильная ссылка на Apartment
экземпляр - из unit4A
переменной. Если вы нарушите эту сильную ссылку, больше не будет сильных ссылок на Apartment
экземпляр:
unit4A = nil
// Prints "Apartment 4A is being deinitialized"
ЗАМЕТКА
В системах, использующих сборку мусора, слабые указатели иногда используются для реализации простого механизма кэширования, поскольку объекты без сильных ссылок освобождаются только тогда, когда давление памяти вызывает сборку мусора. Однако в 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)
. Если вы попытаетесь получить доступ к небезопасной неопознанной ссылке после того, как экземпляр, на который она ссылается, будет освобожден, ваша программа попытается получить доступ к той области памяти, где был этот экземпляр, что является небезопасной операцией.
Неизвестные ссылки и неявно развернутые дополнительные свойства
Приведенные выше примеры слабых и неподтвержденных ссылок охватывают два наиболее распространенных сценария, в которых необходимо разорвать цикл сильных ссылок.
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
экземпляр не будет полностью инициализирован.
Чтобы справиться с этим требованием, вы объявляете capitalCity
свойство Country
как неявно развернутое необязательное свойство, обозначенное восклицательным знаком в конце его аннотации типа ( City!
). Это означает, что capitalCity
свойство имеет значение по умолчанию nil
, как и любой другой необязательный параметр, но к нему можно получить доступ без необходимости разворачивать его значение .
Поскольку 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
класс определяет свойство lazy 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
), замыкание захватывает себя, что означает, что оно удерживает сильную ссылку на HTMLElement
экземпляр. Сильный референтный цикл создается между ними.
ЗАМЕТКА
Несмотря на то, что замыкание ссылается на
self
несколько раз, оно фиксирует только одну сильную ссылку наHTMLElement
экземпляр.
Если вы установите paragraph
переменную равной nil
и прервите ее строгую ссылку на HTMLElement
экземпляр, ни HTMLElement
экземпляр, ни его закрытие не будут освобождены из-за цикла строгой ссылки:
paragraph = nil
Обратите внимание, что сообщение в HTMLElement
деинициализаторе не печатается, что показывает, что HTMLElement
экземпляр не освобожден.
Устранение сильных эталонных циклов для замыканий
Вы разрешаете строгий ссылочный цикл между замыканием и экземпляром класса, определяя список перехвата как часть определения замыкания. Список захвата определяет правила, используемые при захвате одного или нескольких ссылочных типов в теле замыкания. Как и в случае циклов сильных ссылок между двумя экземплярами классов, вы объявляете каждую захваченную ссылку слабой или неподтвержденной ссылкой, а не сильной ссылкой. Соответствующий выбор слабых или неизвестных зависит от отношений между различными частями вашего кода.
ЗАМЕТКА
Swift требует, чтобы вы писали
self.someProperty
илиself.someMethod()
(а не простоsomeProperty
илиsomeMethod()
) всякий раз, когда вы ссылаетесь на членаself
в закрытии. Это поможет вам вспомнить, что это можно сделатьself
случайно.
Определение списка захвата
Каждый элемент в списке захвата представляет собой пару ключевого слова weak
or или unowned
со ссылкой на экземпляр класса (например, self
) или переменную, инициализированную некоторым значением (например, ). Эти пары пишутся в виде пары квадратных скобок, разделенных запятыми.delegate = self.delegate!
Поместите список захвата перед списком параметров замыкания и типом возврата, если они предоставлены:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
Если в замыкании не указан список параметров или тип возвращаемых данных, поскольку они могут быть выведены из контекста, поместите список захвата в самое начало замыкания, а затем введите in
ключевое слово:
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}
Слабые и неизвестные ссылки
Определите перехват в замыкании как неподтвержденную ссылку, когда замыкание и захваченный экземпляр всегда будут ссылаться друг на друга и всегда будут освобождены одновременно.
И наоборот, определите захват как слабую ссылку, когда захваченная ссылка может стать nil
в какой-то момент в будущем. Слабые ссылки всегда имеют необязательный тип и автоматически становятся, nil
когда экземпляр, на который они ссылаются, освобождается. Это позволяет вам проверить их наличие в теле укупорочного средства.
ЗАМЕТКА
Если захваченная ссылка никогда не станет nil
, она должна всегда записываться как не принадлежащая ссылка, а не как слабая ссылка.
Неизвестная ссылка - это подходящий метод захвата, который нужно использовать для разрешения цикла сильных ссылок в HTMLElement
примере из циклов сильных ссылок для замыканий выше. Вот как вы пишете 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]
Вы можете создать и распечатать 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"
0 комментариев