Свифт, почему методам классов не нужны закрывающие списки - PullRequest
0 голосов
/ 09 мая 2018

Если функции по сути являются замыканиями. Почему методам класса не нужны списки замыканий при обращении к self или другому свойству экземпляра внутри замыкания.

Есть ли [незнакомое я] за кадром? Например:

class MyClass{
    func myFunc(){
        self.otherFunc()
    }

    func otherFunc(){
        print()
    }
}

Разве в myFunc не будет ссылочного цикла? Т.е. замыкание указывает на себя, а экземпляр указывает на функцию. Ни один из них не может быть освобожден.

Ответы [ 2 ]

0 голосов
/ 09 мая 2018

"Если функции по сути являются замыканиями." Это не правда Функции (и методы) - это не то же самое, что замыкания. Функции имеют все свои свободные переменные без привязки. Замыкания связывают некоторые или все свои свободные переменные (закрытые над ними, отсюда и название "замыкание").

«Свободная переменная» - это любая переменная, определенная вне области действия функции (включая ее формальные параметры). Функция верхнего уровня func f(x: Int) имеет одну свободную переменную; когда вы вызываете его, вы должны передать параметр. Закрытие типа { f(1) } не имеет свободных переменных. Когда вы вызываете его, вы не передаете никаких параметров.

Метод, как и функция, ничего не захватывает. Он передается всем своим свободным переменным, когда он выполняется. Например, когда вы делаете вызов object.doThis(), это то же самое, что и вызов Type.doThis(object)().

class X {
    func doThis() {}
}

let x = X()
x.doThis()

X.doThis(x)() // Same thing

X.doThis(x) - это функция, которая возвращает функцию. Здесь нет магии. Все свободные переменные предоставляются во время разговора. Ничего не захвачено. («Свободная переменная» в описываемом вами случае - self, но это ничего не меняет. self не является чем-то особенным, за исключением того, что вокруг него появляется немного синтаксического сахара.)

Это отличается от замыкания:

let c = { x.doThis() }
c()

Когда я звоню c(), как он узнает значение x? Возможно, я вернул c, а x может быть вне зоны видимости. Система должна отслеживать x (в том числе делать сильную ссылку, чтобы она не освобождалась), и она делает это, захватывая ее или «замыкаясь по x», что повышает вероятность сохранения циклов. Так что в c, x связано. Это не бесплатно. Вы не можете передать его, когда позвоните c().

self здесь не является особенным. Это просто другая переменная. [weak self] в замыканиях тоже не особенное. Вы также можете написать [weak x]. Синтаксис [...] - это просто список захвата.

0 голосов
/ 09 мая 2018

Затворы могут вызывать циклы задания только тогда, когда затвор остается живым. Учтите это:

let foo = MyClass()
let bar: () -> () = { in
    print(foo)
}

Закрытие bar содержит ссылку на foo, но эта ссылка исчезает, если ничто больше не ссылается на bar. Например:

func f(foo: MyClass) {
    let bar: () -> () = { () in
        print(foo)
    }
}

Это не создает ссылочный цикл, поскольку при возврате f замыкание в bar уничтожается. Точно так же, когда вы вызываете myFunc и otherFunc, вам нужно строгое указание на self (компилятор гарантирует, что он у вас есть), но, поскольку он вам больше не нужен в конце функции, цикл не выполняется. создано.

Как правило, замыкание не будет систематически создавать опорный цикл, даже если оно равно @escaping. Рассмотрим случай Dispatch.async:

class MyClass {
    func foo() {
        DispatchQueue.main.async {
            print(self)
        }
    }
}

Это на самом деле не создает ссылочный цикл, потому что, хотя некоторое время закрытие ссылается на self, self не ссылается на закрытие.

Это опасный случай:

class MyClass {
    var closure: () -> ()

    func f() {
        self.closure = {
            print(self)
        }
    }
}

Этот фактически создает ссылочный цикл: self.closure имеет сильную ссылку на self, а self имеет сильную ссылку на self.closure.

...