Видеокурсы по изучению языка программирования Swift. Подробнее

Замена циклов сильных ссылок для замыканий

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Заменить цикл сильных ссылок между замыканием и экземпляром класса можно путем определения списка захвата в качестве части определения замыкания. Список захвата определяет правила, которые нужно использовать при захвате одного или более ссылочного типа в теле замыкания. Что же касательно циклов сильных связей между двумя экземплярами классов, то вы объявляете каждую захваченную ссылку как слабую или бесхозную (weak или unowned), вместо того, чтобы оставлять ее сильной (strong). Правильный выбор между слабой или бесхозной ссылками зависит от взаимоотношений между различными частями вашего кода.

Заметка

Swift требует от вас написания 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
    // тело замыкания
}

Слабые (weak) или бесхозные (unowned) ссылки

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

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

Заметка

Если захваченная ссылка никогда не будет nil, то она должна быть всегда захвачена как unowned ссылка, а не weak ссылка.

Бесхозная ссылка является подходящим методом захвата для предотвращения существования цикла сильных ссылок в нашем примере с 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) освобождается")
    }
}

Эта реализация HTMLElement идентична предыдущей реализации, кроме дополнения списка захвата внутри замыкания asHTML. В этом случае список захвата [unowned self], который означает: “захватить self как unowned ссылку, вместо strong”.

Вы можете создать и вывести экземпляр 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 освобождается"
Swift: 
3.0