在一个闭包中调用Swift闭包导致堆栈溢出问题

5

更新:此漏洞已被rdar://20931915确认,并在Xcode 7 beta 3中得到修复。


我发现一个奇怪的错误,由于在调试构建中在另一个闭包中调用了Swift闭包而引起。我的Xcode版本是6.3.1,Swift版本是1.2。以下是代码:

import Swift

class ClosureStackOverflow {
    private var b: Bool = false
    private func callClosure1(callback: Void -> Void) {
        println("in closure 1")
        callback()
    }

    private func callClosure2(callback: Void -> Void) {
        println("in closure 2")
        callback()
    }

    func call() {
        callClosure1 { [weak self] in
            self?.callClosure2 {
                self?.b = true
            }
        }
    }
}

let c = ClosureStackOverflow()
c.call()

上面的代码编译得很好。然而,如果你调用它的call()方法,它会无限地打印“in closure 2”,最终导致栈溢出。

请问为什么在一个闭包中调用另一个闭包会导致这个bug呢?

谢谢。


我也发布了一个gist - hankbao
@MartinR 我可以重现这个问题。我正在使用 Apple Swift version 1.2 (swiftlang-602.0.49.6 clang-602.0.49) - Anthony Kong
1
在“发布模式”下,程序会打印“在闭包1中”,“在闭包2中”,然后终止。在“调试模式”下,会发生所描述的“无限”递归和堆栈溢出。同时,这种情况只会出现在对self使用弱引用时,而不是强引用或无主引用。可能是编译器的一个bug。 - Martin R
@MartinR 感谢您指出发布版本和调试版本之间的区别。 - hankbao
那是一个很酷的 bug。你已经为它提出了一个雷达吗?这是唯一的修复方式。 :-) - Warren Burton
是的,我已经提交了一个 bug报告 #20931915。 - hankbao
1个回答

2
将您的代码更改为以下内容,它就可以工作了。
    class ClosureStackOverflow {
    private var b: Bool = false
    private func callClosure1(callback: Void -> Void) {
        println("in closure 1")
        callback()
    }

    private func callClosure2(callback: Void -> Void) {
        println("in closure 2")

        callback()
    }

    func call() {
        callClosure1 {
            self.callClosure2 {
                self.b = true
            }
        }
    }
    deinit{
        print("deinit")
    }
}

看起来你在函数中声明了[weak self],这导致了问题。

我也测试过调用它。

 let c = ClosureStackOverflow()
    c.call()

它将输出:
 in closure 1
in closure 2
deinit

如果你不使用weak self,似乎不会导致循环引用。

此外,我还尝试将函数更改为以下内容:

  func call() {
    callClosure1 {
        [weak self] in
        self!.callClosure2 {
            self?.b = true
        }
    }
}

这也可以正常工作。因此,我认为这可能是Swift编译器的一些bug。


你试过了吗?在我的测试中,你的代码以E_BAD_ACCESS终止。 - Martin R
嘿@WenchenHuang,感谢你的帖子。实际上我知道如何解决这个问题。我只是好奇原因,并请求解释。无论如何,谢谢你。 - hankbao
我认为这是一个编译器的错误。我观察了调用树,它只是陷入了运行循环,从未跳出来。这毫无意义。 - Leo
在callClosure1和callClosure2中设置断点,然后你会发现callClosure2中的回调函数与callClosure1中的回调函数具有相同的地址。很奇怪。 - hankbao
在原始代码的self?.b = true后面添加println("all done"),问题也就消失了。非常奇怪。 - Warren Burton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接