【问题标题】:Structs that refer to each other in Swift 3Swift 3 中相互引用的结构体
【发布时间】:2020-02-14 07:01:19
【问题描述】:

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

struct DetailedPin {
     var pin: Pin?
}

struct Pin {
    var detailedPin: DetailedPin?  
}

但我收到一个错误:Value type 'DetailedPin' cannot have a stored property that references itselfPin 结构也出现同样的错误。我该如何处理这个问题?谢谢。

【问题讨论】:

标签: swift struct


【解决方案1】:

问题在于Optional 将其Wrapped 值内联存储(有关此内容的更多信息,请参阅Mike Ash's fantastic blog post)——这意味着Optional 实例(无论它是否为nil)将占用至少与您希望在其 .some 案例中存储的类型(Wrapped 类型)相同的内存量。

因此,由于您的Pin 结构具有DetailedPin? 类型的属性,而DetailedPin 具有Pin? 类型的属性,因此需要无限存储才能内联存储这些值。

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

但是,如果您希望保留 PinDetailedPin 的值语义,一种选择是创建由类实例支持的包装器类型以提供必要的间接性:

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

【讨论】:

  • 很好的答案! (还有很棒的博文链接,以前没见过 :)
  • 我正在查看您的答案,为什么会这样?数组也是 swift 中的结构。 struct DetailedPin { var pin: [Pin]? } struct Pin { var detailedPin: DetailedPin? }
  • @Rikh 虽然Array 是一个结构,但它的大小是固定的。数组的实际内容是间接存储的,并在变异时被复制以保留值语义。
【解决方案2】:

两个实体之间的一对一关系只有在这些实体之间的至少一个连接是 reference 类型时才有效;对于两种纯值类型,它们的一对一关系将变为递归关系。

假设您创建了一个值类型为DetailedPin 的对象。它包含一个值类型为Pin 的实例属性(pin),这意味着该实例属性是值的一部分,它是DetailedPin 的实例。现在,DetailedPin 的实例属性pinDetailedPin 的实例是值类型Pin,它本身包含值类型detailedPin 的实例属性(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。例如:

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

【讨论】:

    【解决方案3】:

    正如这里提到的: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 告诉编译器你的枚举中有一个递归,它应该插入必要的间接层

    【讨论】:

      【解决方案4】:

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

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-09
        • 2017-03-22
        • 2015-11-06
        相关资源
        最近更新 更多