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свойство, которое изначально nilapartmentСвойство не является обязательным, потому что человек не всегда может иметь квартиру.

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

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

Следующий фрагмент кода определяет две переменные необязательного типа с именем johnand 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Свойство имеет тип , или «функция , которая не принимает никаких параметров и возвращает значение».() -> StringString

По умолчанию 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случайно.

Определение списка захвата

Каждый элемент в списке захвата представляет собой пару ключевого слова weakor или 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переменной в nilHTMLElementэкземпляр освобождается, как это видно из печати его сообщения деинициализатора в следующем примере:

paragraph = nil

// Prints "p is being deinitialized"