UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to deal with object hierarchies and relationships using structs?

Forums > Swift

@mh175  

Swift puts a lot of emphasis on value types, but it seems that it's impossible to create relationships to other objects without resorting to classes. Almost 0 tutorials I've seen online deal with this. Once you use classes and need to archive hierarchies you then need to use NSKeyedArchiver, which means you then need to subclass NSObject and adopt NSCoding (actually NSSecureCoding -- it's now required by Apple). You lose a lot of Swift goodies.

// Value semantics breaks down

struct Dog { }

struct Person {
    var favoriteDog: Dog
}

struct House {
    var dogs = [Dog]()
    var people = [Person]()
}

I've tried various solutions to keep value semantics all the way down by referencing a unique identifier similar to how a database works, but this involves a lot of bookkeeping. It also means you need to pass the entire data model everywhere so you have access to the repository (similar to Core Data's context).

// Store Identifiers

struct Dog {
    let id = UUID()
}

struct Person {
    var favoriteDogID: UUID
}

struct House {
    var dogs = [Dog]()
    var people = [Person]()

    func dog(id: UUID) -> Dog! {
        return self.dogs.first(where: { $0.id == id } )
    }

    mutating func updateDog(_ dog: Dog) {
        var dogs = self.dogs
        let index = self.dogs.firstIndex(where: { $0.id == dog.id } )!
        dogs[index] = dog
        self.dogs = dogs
    }
}

Has anyone found an elegant solution for this? It's a common scenario that goes unaddressed.

3      

You defined a peculiar example with a one-to-many relationship between dogs and people, i.e., each dog can have one or more people for whom that dog is their favorite, but a person cannot exist without a favorite dog. If that's your intention, simply move the people property from the House struct to the Dogs struct. Then you won’t need an id.

But I suspect your example is artificial and abstracted too far from the problem you intend to solve. If you have a real problem in mind, it would help to explain that. And if you need a many-to-many relationship, I expect you should define a relational database instead of trying to simulate one.

3      

hi,

bob makes a decent point: if you're really working with essentially a one-to-many relationship, where the one "owns" the many, put one struct inside the other, although the size of that struct might become prohibitive to copy around, perhaps. but this may not be exactly your situation; and frankly, storing an id in one struct in order to locate a related struct somewhere else is not unique to structs.

for example, Apple has posted some sample code on how to configure Core Data with both a "cloud" and a "local" store, yet still implement a relationship from an object in one of those stores to one in the other store. the key is that an object in one store maintains the id of a related object in the other store, and finding one from the other is implemented using a Core Data fetch (essentially, an optimized firstIndex function).

if you do wish to go the id/lookup route, e.g., to find the person that owns a given dog (with a given id), you could do a similar firstIndex lookup of the people array; but you could also implement a dictionary of type [UUID : Int] in your House struct to quickly locate the correct index within the people array for the dog having the given id. yes, the dictionary would require a little maintenance, but mostly just for adds and deletes, and it would not be much work even if you rebuild it when necessary:

func rebuildLookupDictionary() {
  lookupDictionary.removeAll()
  for index in people.indices {
    lookupDictionary[person[index].favoriteDogID] = index
  }
}

func person(owning dog: Dog) -> Person {
  let index = lookupDictionary[dog.id]!
  return people[index]
}

hope that helps,

DMG

3      

@mh175  

You guys are the best. 🙏 Thank you for taking the time to answer. OK so I'm not totally off track here. Phew. This makes me feel better.

It does seem like I am trying to recreate a relational database but Core Data would be far overkill for my purposes.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.