Свифт, почему методам классов не нужны закрывающие списки
Если функции, по сути, замыкания. Почему методам класса не нужны списки замыканий при обращении к self или другому свойству экземпляра внутри замыкания.
Есть ли за кадром [неизвестное Я]? Например:
class MyClass{
func myFunc(){
self.otherFunc()
}
func otherFunc(){
print()
}
}
Разве в myFunc не будет ссылочного цикла? Т.е. замыкание указывает на себя, а экземпляр указывает на функцию. Ни один не мог быть освобожден.
2 ответа
"Если функции по сути являются замыканиями". Это не правда Функции (и методы) - это не то же самое, что замыкания. Все функции имеют свободные переменные без привязки. Замыкания связывают некоторые или все свои свободные переменные (закрытые над ними, отсюда и название "замыкание").
"Свободная переменная" - это любая переменная, определенная вне области действия функции (включая ее формальные параметры). Функция верхнего уровня 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]
точно также. [...]
синтаксис просто список захвата.
Закрытия могут вызывать контрольные циклы только тогда, когда замыкание остается в живых. Учти это:
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
,