在Swift中,当引用类型的属性彼此之间(或与闭包一起)拥有强烈的所有权时会发生引用循环。
然而,仅限于值类型是否存在引用循环的可能性?
我在playground中尝试过,但没有成功(错误:不允许递归值类型'A')。
struct A {
var otherA: A? = nil
init() {
otherA = A()
}
}
在Swift中,当引用类型的属性彼此之间(或与闭包一起)拥有强烈的所有权时会发生引用循环。
然而,仅限于值类型是否存在引用循环的可能性?
我在playground中尝试过,但没有成功(错误:不允许递归值类型'A')。
struct A {
var otherA: A? = nil
init() {
otherA = A()
}
}
enum Node {
case None(Int)
indirect case left(Int, Node)
indirect case right(Int, Node)
indirect case both(Int, Node, Node)
}
struct Dog { var puppy : Dog? }
。现在考虑以下代码:let d = Dog(); d.puppy = d
。显然这是不连贯的:这就是巴伯悖论。因此,Swift直接阻止了整个情况。事实上,这背后还有更多的原因,与值类型在内存中的实际保存方式有关。但至少这给你一个动机。 - matt免责声明:我在这里对Swift编译器的内部工作进行了(希望是有根据的)猜测,因此请谨慎参考。
除了值语义之外,请问自己:我们为什么要使用结构体?有什么优点?
其中一个优点是,我们可以(即希望)将它们存储在堆栈上(或对象框架中),就像其他语言中的原始值一样。特别是,我们不想分配专用空间指向堆。这使得访问结构体值更加高效:我们(即编译器)总是知道它在内存中的确切位置,相对于当前帧或对象指针。
为了使编译器能够正常工作,它需要知道在确定堆栈或对象框架的结构时为给定的结构体值保留多少空间。只要结构体值是固定大小的树(不考虑对对象的外向引用;它们指向堆不是我们关心的),那就没问题:编译器可以将所有找到的大小相加。
如果您有一个递归的结构体,则会失败:您可以以这种方式实现列表或二叉树。编译器无法静态地确定如何在内存中存储这些值,因此我们必须禁止它们。
请注意:相同的推理解释了为什么结构体是按值传递的:我们需要它们在新上下文中物理存在。
快速简便的解决方法:将其嵌入数组中。
struct A {
var otherA: [A]? = nil
init() {
otherA = [A()]
}
}
class MyObject {
static var objCount = 0
init() {
MyObject.objCount += 1
print("Alloc \(MyObject.objCount)")
}
deinit {
print("Dealloc \(MyObject.objCount)")
MyObject.objCount -= 1
}
}
struct MyValueType {
var closure: (() -> ())?
var obj = MyObject()
init(leakMe: Bool) {
if leakMe {
closure = { print("\(self)") }
}
}
}
func test(leakMe leakMe: Bool) {
print("Creating value type. Leak:\(leakMe)")
let _ = MyValueType(leakMe: leakMe)
}
test(leakMe: true)
test(leakMe: false)
输出:
Creating value type. Leak:true
Alloc 1
Creating value type. Leak:false
Alloc 2
Dealloc 2
Closure cannot implicitly capture a mutating self parameter
。因此,编译器防止了这种保留循环! - blackjacx然而,仅使用值类型是否存在引用循环的可能性?
这取决于您所谓的“仅使用值类型”是指什么。 如果您指的是完全没有包含隐藏引用在内的任何引用,那么答案就是否定的。 要创建一个引用循环,至少需要一个引用。
但在Swift中,Array、String或其他一些类型是值类型,它们可能在其实例中包含引用。如果您的“值类型”包括这些类型,则答案是肯定的。