C# Swift中的转义闭包与C中的委托类型#

C# Swift中的转义闭包与C中的委托类型#,c#,swift,closures,C#,Swift,Closures,当我最近开始学习Swift时,我的心态是C#型的。我发现很难理解,如果Swift函数想要存储闭包类型参数,它必须标记为@escaping 在C#中,如果委托稍后将在当前范围之外存储和调用,则这种等价性不需要使用任何特殊关键字标记委托类型参数 Swift中需要@escaping关键字的闭包有什么特殊之处?关于此语言特性的任何实现细节都将非常有用 Swift中的转义闭包没有什么特别之处。关键字用于告诉API调用方闭包将超出调用API的范围。这对Swift中的内存管理具有重要意义 Swift中没有真正

当我最近开始学习Swift时,我的心态是C#型的。我发现很难理解,如果Swift函数想要存储闭包类型参数,它必须标记为
@escaping

在C#中,如果委托稍后将在当前范围之外存储和调用,则这种等价性不需要使用任何特殊关键字标记委托类型参数


Swift中需要
@escaping
关键字的闭包有什么特殊之处?关于此语言特性的任何实现细节都将非常有用

Swift中的转义闭包没有什么特别之处。关键字用于告诉API调用方闭包将超出调用API的范围。这对Swift中的内存管理具有重要意义

Swift中没有真正的垃圾收集器。你可能意识到你可以有一个强引用或弱引用。强引用是指只要它存在,就不能释放引用的对象。弱引用是指即使对象仍然存在,也可以释放该对象的引用

例如:

class A
{
    var foo: B?
    weak var bar: B?
}

class B
{
    var foo: A?
    weak var bar: A?
}

var a = A()
do 
{
    var b1 = B()
    var b2 = B()
    a.foo = b1
    a.bar = b2
}
在上面的例子中,
a
b1
有强引用,而对
b2
有弱引用。当
do
的范围退出时,
b2
将被解除分配,但
b1
将继续存在,因为
a
对其有很强的引用

如果我这样做:

b1.foo = a
现在
a
强烈引用
b1
b1
强烈引用
a
。当这些对象超出范围时,它们不会被解除分配,因为每个对象都有一个对另一个对象的强引用。它被称为引用循环,是Swift中内存泄漏的主要来源。他们绕开它的方法是将一个对象指定为另一个对象的“所有者”,并使所有者对所有者的引用成为弱引用

闭包是Swift中的一个对象,它的属性是要执行的代码块和对它捕获的变量的引用。默认情况下,这些是强引用。因此,这次结束:

{ () -> () in print(a) }
捕获
a
作为强引用。这很好,但有一个问题。很容易意外地创建引用循环。考虑以下事项:

class C
{
    private var frobnicator: () -> () = { }

    func doFrobnication()
    {
        frobnicator()
    }

    var aString = "a value"

    func setFrobnicator(_ closure: @escaping () -> ())
    {
        frobnicator = closure
    }

    func temporarilyFrobnicate(_ closure: () -> ())
    {
        closure()
    }
}

let c = C()
c.setFrobnicator{ print(c.aString) }
c.temporarilyFrobnicate{ print(c.aString) }

在上述两个调用中,
c
由带有字符串引用的闭包捕获。对于第二种情况,这是可以接受的。闭包强烈引用了
c
,但没有任何内容强烈引用闭包。闭包不属于
临时frobnite
的范围。但是,在第一种情况下,
c
强烈引用了闭包,闭包强烈引用了
c
。我们有一个参考周期。对于调用函数(如
setFrobInstactor
)的程序员来说,重要的是要知道闭包超出了函数的作用域,因此他们可以使捕获引用变弱,例如

c.setFrobnicator{ [weak c] in print(c.aString ?? "") }
该问题对
self
尤其有害,例如,如果您尝试以下操作:

extension C
{
    func doSomething()
    {
        setFrobnicator{ print(aString) }
        temporarilyFrobnicate{print(aString) }
    }
}
看起来您没有创建任何引用循环,但是通过使用
aString
您实际上捕获了
self
。这就是为什么编译器会在
setfrobInstactor{print(aString)}
上标记错误,以使您显式地编写
self
,这样您就可以明显地捕获到它,并且您应该确保捕获得很弱

extension C
{
    func doSomething()
    {
        setFrobnicator{ [weak self] in print(self.aString ?? "") }
        temporarilyFrobnicate{print(aString) }
    }
}

编译器不会在第二次调用时出错,因为它知道闭包没有转义,就像您所做的那样。

@当闭包在作为参数的函数完成执行后被调用时,会使用转义。但是我不能说为什么/如何这对Swift是特别的,因为我不知道C。关键是我们在Swift中区分了
@转义
和非转义闭包,因为编译器可以使用非转义闭包执行某些优化。