Swift中未使用捕获列表的闭包调用异常原因解析
问题解析:闭包捕获列表与递归引用陷阱
咱们直接拆解你代码里的两个核心场景,把背后的逻辑说透:
为什么无捕获列表的闭包会崩溃/无法运行?
先看这段出问题的代码:
someClosure = { (left : Int, right : Int) in someClosure(left , right) }
这里的闭包没有写捕获列表,它会隐式强绑定到someClosure这个变量本身(注意,是变量,不是变量当时的取值),这会触发两个致命问题:
- 无限递归死循环:当你调用
someClosure(2,3)时,闭包内部会再次调用someClosure——但此时someClosure已经指向了这个闭包自己,于是就陷入了无限递归调用,最终会触发栈溢出错误(没错,就是咱们这个网站的名字😉),程序直接崩溃。 - 强引用循环:闭包强持有
someClosure变量,而someClosure变量又持有闭包的引用,两者互相攥着对方不放,就算程序没因为递归崩溃,这部分内存也永远无法被释放——不过在这个案例里,递归崩溃会先发生。
为什么添加捕获列表后就能正常运行?
再看这段能正常工作的代码:
someClosure = { [someClosure] (left : Int, right : Int) in someClosure(left , right) }
这里的[someClosure]是值捕获(默认是强捕获,你也可以写[weak someClosure]或者[unowned someClosure],不过这里捕获的是函数引用,不会有循环问题),它的核心作用是:
- 在闭包创建的那一刻,把
someClosure变量当时指向的内容(也就是最初的biggerOne函数引用)“快照”一份,保存到闭包内部。 - 之后
someClosure变量被重新赋值为这个闭包时,闭包内部调用的someClosure是捕获下来的旧值——也就是原来的biggerOne函数,而不是现在的闭包自己。
所以当你调用someClosure(2,3)时,闭包内部实际调用的是biggerOne(2,3),返回3,完全不会触发递归,自然就能正常运行了。
补充:再理解捕获列表的本质
捕获列表的核心是在闭包创建时锁定外部变量的当前值,而不是在闭包执行时去读取变量的最新状态。没有捕获列表的闭包,会直接绑定到外部变量本身,每次执行时都会去读变量的当前状态——这就是两者最关键的区别。
如果想要更直观验证,你可以打印内存地址看看:
print("初始地址:", Unmanaged.passUnretained(someClosure as AnyObject).toOpaque()) someClosure = { [someClosure] (left, right) in print("闭包内捕获的地址:", Unmanaged.passUnretained(someClosure as AnyObject).toOpaque()) return someClosure(left, right) } print("新地址:", Unmanaged.passUnretained(someClosure as AnyObject).toOpaque())
你会看到闭包内打印的地址是初始biggerOne的地址,而新地址是闭包自己的地址,这就把捕获列表的作用展示得明明白白。
内容的提问来源于stack exchange,提问作者2nebin
相关产品推荐
相关产品推荐

