c# - Swift 中的闭包转义 VS C# 中的委托(delegate)类型

标签 c# swift closures

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

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

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

现在 ab1 具有强引用,而 b1a 具有强引用。这些对象在超出范围时不会被释放,因为每个对象都有对另一个的强引用。它被称为引用循环,它是 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 有强引用,但没有对闭包的强引用。闭包不会脱离 temporarilyFrobnicate 的范围。但是,在第一种情况下,c 对闭包有强引用,而闭包对 c 有强引用。我们有一个引用周期。对于调用像 setFrobnicator 这样的函数的程序员来说,知道闭包脱离了函数的范围很重要,这样他们就可以使捕获引用变弱,例如

c.setFrobnicator{ [weak c] in print(c.aString ?? "") }

这个问题对 self 尤其有害,例如如果你试试这个:

extension C
{
    func doSomething()
    {
        setFrobnicator{ print(aString) }
        temporarilyFrobnicate{print(aString) }
    }
}

看起来您并没有创建任何引用循环,但通过使用 aString,您实际上捕获了 self。这就是为什么编译器会在 setFrobnicator{ print(aString) } 上标记一个错误,让您显式地编写 self 以便很明显您已经捕获了它并且您应该确保它被弱捕获。

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

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

关于c# - Swift 中的闭包转义 VS C# 中的委托(delegate)类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58969918/

相关文章:

c# - 在 xamarin.android 中获取 IoC 容器

ios - 如何确保 WidgetKit View 显示来自 @FetchRequest 的正确结果?

ios - 在类中定义的闭包中到达 "self"

closures - 为什么 `take_while` 的闭包通过引用来获取它的参数?

swift - 由于未捕获的异常 'NSInvalidArgumentException' 无法识别的选择器发送到实例 0x7fdab8596b40' 而终止应用程序

ios - 如何将计算的闭包属性转换为 Swift 中的闭包?

C# 列表比较器使用两个比较元素

C# - 无法在方法内声明委托(delegate)

c# - 如何更改行 MouseOver 上的 GridView 单元格颜色

ios - Xcode 8.2 Playground 中 'PAGE' 描述的格式是哪个?