在Swift 3中相互引用的结构

时间:2016-11-23 18:22:53

标签: swift struct

我有两个具有一对一关系的CoreData实体。我想基于这个实体创建结构。 我的代码:

struct DetailedPin {
     var pin: Pin?
}

struct Pin {
    var detailedPin: DetailedPin?  
}

但我收到了一个错误:Value type 'DetailedPin' cannot have a stored property that references itself。和Pin struct的错误相同。我该如何处理这个问题?感谢。

4 个答案:

答案 0 :(得分:27)

问题在于Optional将其Wrapped值内联存储(有关详细信息,请参阅Mike Ash's fantastic blog post) - 表示Optional实例(无论是否为nil.some是否会占用至少与您希望在Wrapped案例中存储的类型(Pin类型)相同的内存量。

因此,由于您的DetailedPin?结构具有DetailedPin类型的属性,并且Pin?具有类型Pin的属性,因此需要无限存储空间来存储这些值是内联的。

因此,解决方案只是添加一个间接层。这样做的一种方法是将DetailedPin和/或class引用类型(即Pin)设为@dfri has suggested

但是,如果您希望保留DetailedPin/// Provides indirection for a given instance. /// For value types, value semantics are preserved. struct Indirect<T> { // Class wrapper to provide the actual indirection. private final class Wrapper { var value: T init(_ value: T) { self.value = value } } private var wrapper: Wrapper init(_ value: T) { wrapper = Wrapper(value) } var value: T { get { return wrapper.value } set { // Upon mutation of value, if the wrapper class instance is unique, // mutate the underlying value directly. // Otherwise, create a new instance. if isKnownUniquelyReferenced(&wrapper) { wrapper.value = newValue } else { wrapper = Wrapper(newValue) } } } } 的值语义,一个选项是创建一个由类实例支持的包装器类型,以提供必要的间接:

Indirect

您现在可以只使用struct DetailedPin { private var _pin = Indirect<Pin?>(nil) // Convenience computed property to avoid having to say ".value" everywhere. var pin: Pin? { get { return _pin.value } set { _pin.value = newValue } } } struct Pin { var detailedPin: DetailedPin? var foo: String } var d = DetailedPin() var p = Pin(detailedPin: d, foo: "foo") d.pin = p // testing that value semantics are preserved... var d1 = d d1.pin?.foo = "bar" print(d.pin?.foo as Any) // Optional("foo") print(d1.pin?.foo as Any) // Optional("bar") 包装器来构建一个(或两个)结构属性:

const canvas1 = document.getElementById('myCanvas');
const ctx = canvas1.getContext('2d');

ctx.font = '15px Calibri';

const canvas2 = document.getElementById('myCanvas');
console.log(canvas2.getContext('2d').font); // -> "15px Calibri"

答案 1 :(得分:4)

两个实体之间的一对一关系仅在这些实体之间的至少一个连接属于引用类型时有效;对于两个纯值类型,它们的一对一关系将成为递归的。

假设您创建值类型为DetailedPin的对象。它包含值类型pin的实例属性(Pin),这意味着此实例属性是值{/ em>的一部分,它是DetailedPin的实例。现在,pin的实例属性DetailedPinPin的实例属于值类型detailedPin,它本身包含值类型为{{1}的实例属性(DetailedPin) }}。此实例成员detailedPin同样是_实例pin)的一部分,但同样,detailedPin本身拥有类型pin的值,因此递归舞蹈继续......

您可以通过将一个结构转换为引用类型(class)来避免这种情况:

struct DetailedPin {
   var pin: Pin?
}

class Pin {
    var detailedPin: DetailedPin?
}

// or
class DetailedPin {
   var pin: Pin?
}

struct Pin {
    var detailedPin: DetailedPin?
}

请注意,上面提到的递归关系与ARC(自动引用计数)或强引用周期没有直接关系,而是值类型实例的值是实例本身和值它包含的所有值类型(子)属性(以及它包含的所有引用类型(子)属性的引用)。

如果您选择让一对一实体成为引用类型,请注意:在这种情况下,您必须确保两种类型之间的引用之一为weak。 E.g:

class DetailedPin {
    weak var pin: Pin?
}

class Pin {
    var detailedPin: DetailedPin?
}

// or
class DetailedPin {
    var pin: Pin?
}

class Pin {
    weak var detailedPin: DetailedPin?
}

如果您遗漏weak以上(即,两者都通过默认的强引用相互引用),您将在两个实例之间引用一个强引用循环

class DetailedPin {
    var pin: Pin?
    deinit { print("DetailedPin instance deinitialized") }
}

class Pin {
    var detailedPin: DetailedPin?
    deinit { print("Pin instance deinitialized") }
}

func foo() {
    let pin = Pin()
    let detailedPin = DetailedPin()
    pin.detailedPin = detailedPin
    detailedPin.pin = pin
}

foo() // no deinit called

答案 2 :(得分:2)

如前所述:indirect enums and structs,一个优雅的解决方案可能是将您的引用包含在带有indirect大小写的枚举中。

在你的例子中,它会给出:

enum ReferencePin {
  case none
  indirect case pin(Pin)
}

struct DetailedPin {
     var pin: ReferencePin
}

struct Pin {
    var detailedPin: DetailedPin?
}

或直接根据您的需要:

enum ReferencePin {
  case none
  indirect case pin(Pin)
}

struct Pin {
    var otherPin: ReferencePin
}

除了我的理解之外,indirect告诉编译器你的枚举中有一个递归,它应该插入必要的间接层

答案 3 :(得分:2)

这应该可以满足您的需求:

struct DetailedPin {
    private var _pin: Any?

    var pin: Pin? {
        get {
            return _pin as? Pin
        }
        set {
            _pin = newValue
        }
    }
}

struct Pin {
    private var _detailedPin: Any?

    var detailedPin: DetailedPin? {
        get {
            return _detailedPin as? DetailedPin
        }
        set {
            _detailedPin = newValue
        }
    }
}

用法:

var pin = Pin()
var detailedPin = DetailedPin()

pin.detailedPin = detailedPin