Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Structs that refer to each other in Swift 3

Tags:

struct

swift

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

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.

like image 622
RomanHouse Avatar asked Nov 23 '16 18:11

RomanHouse


People also ask

Can you put a struct in a struct Swift?

Swift allows us to create property and methods that belong to a struct, rather than an instance of the struct. This means you can access the properties or methods even if the instance of struct is not created. These shared properties in swift are called static properties.

Can we inherit struct in Swift?

A struct cannot inherit from another kind of struct, whereas classes can build on other classes. You can change the type of an object at runtime using typecasting. Structs cannot have inheritance, so have only one type. If you point two variables at the same struct, they have their own independent copy of the data.

Are structs passed by value Swift?

In Swift, instances of classes are passed by reference. This is similar to how classes are implemented in Ruby and Objective-C. It implies that an instance of a class can have several owners that share a copy. Instances of structures and enumerations are passed by value.

Why struct is faster than class Swift?

Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple references to the same instance as happens with classes. This is especially important when passing around a variable to many classes and/or in a multithreaded environment.


1 Answers

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

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.

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.

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:

/// 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")
like image 92
Hamish Avatar answered Nov 09 '22 00:11

Hamish