繁体   English   中英

Swift——闭包中的局部变量

[英]Swift – Local variables in a closure

我正在研究 Swift 的闭包,但我对闭包捕获列表有一些疑问。 我知道闭包是一种引用类型。 我知道引用类型和值类型的含义。 但是在闭包中,当它们捕获值(闭包的局部变量)时,局部变量怎么可能是引用类型呢?

例如:

var i: Int = 0

var closureArray: [() -> ()] = []

for _ in 1...5 {
    closureArray.append { print(i) }
    i += 1
}

我认为这是闭包中最著名的捕获列表示例。 然后我以为i是整数类型,它也是值类型但是i怎么能成为闭包块中的引用类型呢?

这与 JavaScript 中的作用域链是同一个概念吗? 谁能解释清楚?

虽然闭包本身是引用类型,但它捕获的局部变量不一定是引用。 如果您在概念上将闭包视为具有捕获变量作为属性的class的可执行实例,您会惊讶地接近它们的实现方式(除非它们被标记为@convention(c) ,在这种情况下它们只是函数指针,但在那种情况下,它们无法捕获任何东西)。 所有这些都需要优化,例如内联。

当然,如果局部变量本身是引用类型,比如class的实例,则通过引用捕获。

不过,不可变的局部值类型变量是按值捕获的。 例如,如果您的代码是:

for i in 1...5 {
    closureArray.append { print(i) }
}

closureArray.forEach { $0() }

你会得到

1
2
3
4
5

可变局部值类型变量通过引用捕获,其工作方式与函数的inout参数非常相似。 在幕后,实际捕获的是指向可变值的指针。

最初,我说这样的闭包不能是@escaping ,而且我很确定这在某一点上是正确的,但你的问题促使我做了一个实验,表明情况并非如此:

func foo() -> [() -> ()]
{
    var i: Int = 0
    var closureArray: [() -> ()] = []

    for _ in 1...5 {
        closureArray.append { print(i); i += 1 }
    }
    
    return closureArray
}

foo().forEach { $0() }

闭包通过返回的Array转义函数作用域,并通过引用捕获局部变量i 尽管在foo返回后运行了闭包,但输出是:

0
1
2
3
4

我什至尝试在从foo获取闭包数组和调用闭包之间彻底粉碎堆栈之间运行一系列深度嵌套的函数,但它对输出没有影响。

看起来它是i而不是实际将它放在堆栈上。 这意味着 Swift 也将局部变量装箱(即在闭包外部的foo中使用时)。 这是有道理的,因为它允许安全地执行闭包,尽管它确实引入了一些潜在的性能下降,因为必须动态分配盒子。 我将代码放入 Compiler Explorer 中。 这是var i: Int = 0行的英特尔程序集:

        add     rdi, 16
        mov     esi, 24
        mov     edx, 7
        call    swift_allocObject@PLT
        mov     qword ptr [rbp - 160], rax
        mov     rcx, rax
        add     rcx, 16
        mov     qword ptr [rbp - 152], rcx
        mov     qword ptr [rbp - 16], rcx
        mov     qword ptr [rax + 16], 0

请注意对swift_allocObject@PLT的调用。 那是为i动态分配盒子。 所以foo和闭包在访问i时都使用的是指向该分配内存的指针。

作为比较,如果我完全删除闭包:

func foo()
{
    var i: Int = 0

    for _ in 1...5 {
        print(i); i += 1
    }
    
}

现在对应于var i: Int = 0的流水线只是一条指令:

        mov     qword ptr [rbp - 16], 0

因此捕获可变变量不仅会影响变量在闭包中的访问方式,还会影响声明该变量的范围。

我不会用 JavaScript 编程,所以我没有资格将 Swift 中捕获的变量与 JavaScript 的任何特性进行比较。

Swift 中的语义

没错,Swift 闭包是reference类型。 然而,Swift 闭包捕获变量本身——既不是变量的值,也不是这些变量指向的引用的值。 因此,我们不应该关心i有什么语义。

其次,由于您正在捕获print方法,因此当您调用closureArray时,结果将如下所示:

[() -> (), () -> (), () -> (), () -> (), () -> ()]

关于 JavaScript 中的作用域链

Scope chain意味着一个变量有一个作用域(它可能是全局或局部/函数或块作用域)被另一个具有另一个作用域(可能是全局或局部/函数或块作用域)的变量或函数使用。 这个完整的链条结构在用户希望停止时根据需要进行和停止。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM