self.someProperty
или так как замыкание вызовет метод на этом объекте, такой как self.someMethod()
. В любом случае, эти доступы вызовут захватывание замыканием self
, создавая цикл сильных ссылок.
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.
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)\(heading.name)>"
}
print(heading.asHTML())
// Выведет "<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())
// Выведет "<p>hello, world</p>"
Переменная paragraph
выше определена как опциональный HTMLElement
, так как он может быть установлен в nil
ниже для демонстрации создания цикла сильных ссылок.
HTMLElement
, как написано выше, создаёт цикл сильных ссылок между объектом HTMLElement
и замыканием, используемым в качестве дефолтного значения asHTML
. Этот цикл выглядит так:
asHTML
объекта держит сильную ссылку на своё замыкание. Однако так как замыкание ссылается на self
из своего тела (как способ сослаться на self.name
и self.text
), то замыкание захватывает self
, что значит, что он удерживает обратную сильную ссылку на объект HTMLElement
. Цикл сильных ссылок создан между ними двумя.
self
несколько раз, оно захватывает сильную ссылку на объект HTMLElement
.
paragraph
в nil
и разобъёте его сильную ссылку на объект HTMLElement
, то ни HTMLElement
объект, ни замыкание не будут деаллоцированы, так как будет создан цикл сильных ссылок:
paragraph = nil
Заметьте, что сообщение в деинициализаторе HTMLElement
не выводится, что демонстрирует то, что HTMLElement
объект не будет деаллоцирован.
self.someProperty
или self.someMethod()
(а не просто someProperty
или someMethod()
), когда Вы ссылаетесь на члена self
из замыкания. Этот помогает Вам помнить, что существует возможность захватить self
по ошибке.
weak
или unowned
с ссылкой на объект класса (например, self
) или переменную, инициализированную каким-то значением (как например, delegate = self.delegate!
). Эти пары пишутся внутри пары квадратных скобок, разделённых запятыми.
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// здесь будет тело замыкания
}
in
:
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// здесь будет тело замыкания
}
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]
, который означает "захватить self
в качестве невладеющей ссылки, а не сильной".
HTMLElement
как и раньше:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Выведет "<p>hello, world</p>"
Здесь Вы можете увидеть, как ссылки выглядят ввиду применения листа захвата:
self
замыканием происходит по невладеющей ссылке и не создаёт сильного удержания объекта HTMLElement
, который он захватывает, если Вы установите значение сильной ссылки переменной paragraph
в nil
, то объект HTMLElement
будет деаллоцирован, что можно увидеть по выводу сообщения его деинициализатором в примере ниже:
paragraph = nil
// Выведет "p is being deinitialized"