在Swift 3中相互引用的结构

时间:2021-07-10 12:21:50

I have two CoreData entities which have one to one relationship. I want to create structs based on this entities. My code:

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

struct DetailedPin {
     var pin: Pin?
}

struct Pin {
    var detailedPin: DetailedPin?  
}

But I got an error: Value type 'DetailedPin' cannot have a stored property that references itself. And the same error for Pin struct. How can I handle this issue? Thanks.

但是我收到一个错误:值类型'DetailedPin'不能有一个引用它自己的存储属性。和Pin结构的错误相同。我该如何处理这个问题?谢谢。

4 个解决方案

#1


22  

The problem is that an Optional stores its Wrapped value inline (see Mike Ash's fantastic blog post for more info about this) – meaning that an Optional instance (regardless of whether it is nil or not) will occupy at least the same amount of memory as the type you wish to store in its .some case (the Wrapped type).

问题是,一个Optional将其Wrapped值内联存储(有关此内容的更多信息,请参阅Mike Ash的精彩博文) - 这意味着一个Optional实例(无论是否为n)将占用至少相同的内存量您希望存储在.some案例中的类型(Wrapped类型)。

Thus, as your Pin struct has a property of type DetailedPin?, and DetailedPin has a property of type Pin?, infinite storage would be required in order to store these values inline.

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

The solution therefore is simply to add a layer of indirection. One way of doing this would be to make Pin and/or DetailedPin a reference type (i.e a class) as @dfri has suggested.

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

However, if you wish to keep the value semantics of Pin and DetailedPin, one option would be to create a wrapper type backed by a class instance to provide the necessary indirection:

但是,如果您希望保留Pin和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)
      }
    }
  }
}

You can now just use the Indirect wrapper for one (or both) of your structs properties:

您现在可以只使用间接包装器来构建一个(或两个)结构属性:

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")

#2


4  

A one to one relationship between two entities is only valid if at least one of the connections between these entities is of reference type; for two pure value types, their one to one relationsship will become a recursive one.

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

Say you create an object of the value type DetailedPin. It contains an instance property (pin) of the value type Pin, which means this instance property is part of the value that is the instance of DetailedPin. Now, the instance property pin of and instance of DetailedPin, is of the value type Pin, which itself contains an instance property (detailedPin) of value type DetailedPin. This instance member detailedPin is, again, _a part of the value that is the instance pin), but again, detailedPin itself owns a value of type pin, and so the recursive dance continues ...

假设您创建值类型为DetailedPin的对象。它包含值类型Pin的实例属性(引脚),这意味着此实例属性是作为DetailedPin实例的值的一部分。现在,DetailedPin的实例属性引脚和实例具有值类型Pin,它本身包含值类型DetailedPin的实例属性(detailedPin)。此实例成员detailedPin再次是_实例引脚值的一部分,但同样,detailedPin本身拥有类型为pin的值,因此递归舞蹈继续......

You can circumvent this by turning one of your structures into a reference type (class):

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

struct DetailedPin {
   var pin: Pin?
}

class Pin {
    var detailedPin: DetailedPin?
}

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

struct Pin {
    var detailedPin: DetailedPin?
}

Note that the recursive relationship covered above is not directly related to ARC (automatic reference counting) or strong reference cycles, but the fact that the value of a value type instance is the instance itself and the value of all value type (sub-)properties it contains (as well as the reference to all reference type (sub-)properties it contains).

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

Just take care if you choose to let both your one to one entities be reference types: in this case you must make sure that one of the references between the two types are 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?
}

In case you leave out weak above (i.e., let both refer to each other by the default strong references), you will have a strong reference cycle between the two instances referring to each other

如果你忽略了上面的弱点(即,让两者都通过默认的强引用相互引用),你将在两个实例之间有一个强引用周期

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

#3


2  

This should accomplish what you need:

这应该完成你需要的:

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
        }
    }
}

Usage:

用法:

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

pin.detailedPin = detailedPin

#4


1  

As mentionned here: indirect enums and structs, an elegant solution could be to wrap your reference inside an enum with an indirect case.

正如这里提到的:间接枚举和结构,一个优雅的解决方案可能是用间接情况将你的引用包装在枚举中。

In you example, it would give this:

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

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

struct DetailedPin {
     var pin: ReferencePin
}

struct Pin {
    var detailedPin: DetailedPin?
}

or directly, depending on your needs:

或直接,根据您的需要:

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

struct Pin {
    var otherPin: ReferencePin
}

Beyond of my understanding, indirect tells the compiler there is a recursion in your enum, and it should insert the necessary layer of indirection.

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

#1


22  

The problem is that an Optional stores its Wrapped value inline (see Mike Ash's fantastic blog post for more info about this) – meaning that an Optional instance (regardless of whether it is nil or not) will occupy at least the same amount of memory as the type you wish to store in its .some case (the Wrapped type).

问题是,一个Optional将其Wrapped值内联存储(有关此内容的更多信息,请参阅Mike Ash的精彩博文) - 这意味着一个Optional实例(无论是否为n)将占用至少相同的内存量您希望存储在.some案例中的类型(Wrapped类型)。

Thus, as your Pin struct has a property of type DetailedPin?, and DetailedPin has a property of type Pin?, infinite storage would be required in order to store these values inline.

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

The solution therefore is simply to add a layer of indirection. One way of doing this would be to make Pin and/or DetailedPin a reference type (i.e a class) as @dfri has suggested.

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

However, if you wish to keep the value semantics of Pin and DetailedPin, one option would be to create a wrapper type backed by a class instance to provide the necessary indirection:

但是,如果您希望保留Pin和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)
      }
    }
  }
}

You can now just use the Indirect wrapper for one (or both) of your structs properties:

您现在可以只使用间接包装器来构建一个(或两个)结构属性:

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")

#2


4  

A one to one relationship between two entities is only valid if at least one of the connections between these entities is of reference type; for two pure value types, their one to one relationsship will become a recursive one.

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

Say you create an object of the value type DetailedPin. It contains an instance property (pin) of the value type Pin, which means this instance property is part of the value that is the instance of DetailedPin. Now, the instance property pin of and instance of DetailedPin, is of the value type Pin, which itself contains an instance property (detailedPin) of value type DetailedPin. This instance member detailedPin is, again, _a part of the value that is the instance pin), but again, detailedPin itself owns a value of type pin, and so the recursive dance continues ...

假设您创建值类型为DetailedPin的对象。它包含值类型Pin的实例属性(引脚),这意味着此实例属性是作为DetailedPin实例的值的一部分。现在,DetailedPin的实例属性引脚和实例具有值类型Pin,它本身包含值类型DetailedPin的实例属性(detailedPin)。此实例成员detailedPin再次是_实例引脚值的一部分,但同样,detailedPin本身拥有类型为pin的值,因此递归舞蹈继续......

You can circumvent this by turning one of your structures into a reference type (class):

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

struct DetailedPin {
   var pin: Pin?
}

class Pin {
    var detailedPin: DetailedPin?
}

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

struct Pin {
    var detailedPin: DetailedPin?
}

Note that the recursive relationship covered above is not directly related to ARC (automatic reference counting) or strong reference cycles, but the fact that the value of a value type instance is the instance itself and the value of all value type (sub-)properties it contains (as well as the reference to all reference type (sub-)properties it contains).

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

Just take care if you choose to let both your one to one entities be reference types: in this case you must make sure that one of the references between the two types are 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?
}

In case you leave out weak above (i.e., let both refer to each other by the default strong references), you will have a strong reference cycle between the two instances referring to each other

如果你忽略了上面的弱点(即,让两者都通过默认的强引用相互引用),你将在两个实例之间有一个强引用周期

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

#3


2  

This should accomplish what you need:

这应该完成你需要的:

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
        }
    }
}

Usage:

用法:

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

pin.detailedPin = detailedPin

#4


1  

As mentionned here: indirect enums and structs, an elegant solution could be to wrap your reference inside an enum with an indirect case.

正如这里提到的:间接枚举和结构,一个优雅的解决方案可能是用间接情况将你的引用包装在枚举中。

In you example, it would give this:

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

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

struct DetailedPin {
     var pin: ReferencePin
}

struct Pin {
    var detailedPin: DetailedPin?
}

or directly, depending on your needs:

或直接,根据您的需要:

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

struct Pin {
    var otherPin: ReferencePin
}

Beyond of my understanding, indirect tells the compiler there is a recursion in your enum, and it should insert the necessary layer of indirection.

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