0

OK, first, I know that there is no such thing as AnyRealmObject.

But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.

Currently, I have something like this:

enter code here
class Family: Object {
   var pets: List<Pet>
}

class Pet: Object {
    var dog: Dog?
    var cat: Cat?
    var rabbit: Rabbit?
}

Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.

What I really want to do is this:

class Family: Object {
  var pets: List<Object>
}

Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>

The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.

Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:

class ObjectReference: Object {
   var className: String
   var primaryKeyValue: String

   public init(with object: Object) {
      className = ???
      primaryKeyValue = ???
   }

   public func object() -> Object? {
      guard let realm = realm else { return nil }
      var type = ???
      var primaryKey: AnyObject = ???
      return realm.object(ofType: type, forPrimaryKey: primaryKey)(
   }
}

The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.

Dan Morrow
  • 4,433
  • 2
  • 31
  • 45
  • As you know, and from the Realm docs *Lists only hold Objects of a single subclass type.*. That being said, a generic property would offer more flexibility. Instead of the Pet properties dog, cat, rabbit, how about a property *type_of_pet* and the value would be dog, cat, mouse. Or even more generic, a *ThingClass* object with a var X property. I think a use case example would clarify the question (i.e. *why* do you want to store a random jumble of different objects) – Jay Oct 08 '17 at 14:11
  • Well, they're not a "random jumble". They are things that are somewhat related. They all support the "Pet" protocol, for instance. I could make them into an [AnyObject] array, and cycle through that, and pull out the common properties I need to display info about them. But they are different object-types. – Dan Morrow Oct 08 '17 at 16:18
  • I was using random jumble figuratively. ;-) It's still unclear why AnyObject is needed when they objects are of type Pet. Maybe providing a specific use case would give us enough information to craft a solution. – Jay Oct 08 '17 at 16:53
  • OK, perhaps that wasn't the best example. Say I have some kind of home inventory. I have books (properties: ["name", "image", "author", "copyright"]) and baseball cards (properties: ["name", "image", "team", "stats"]) and coffee mugs (properties: ["name", "image", "liters"]). These are all different, but each has a "name" and "image". If I wanted to display all of them in table-view, for example, I could have them all adhere to the "NameImage" protocol, and display them. So, I'd like a "MyStuff" object, with property of `List` - but I can't do that. – Dan Morrow Oct 11 '17 at 17:34

2 Answers2

1

(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)

Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:

public func object() -> Object? {
    let applicationName = // (application name goes here)

    guard let realm = realm else { return nil }
    guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
        print("Error: \(className) isn't the name of a Realm class.")
        return nil
    }
    var primaryKey: String = primaryKeyValue
    return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}

My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)

In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).

AustinZ
  • 1,787
  • 1
  • 13
  • 21
0

This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.

While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...

Let's define some Realm objects

class BookClass: Object {
    @objc dynamic var author = ""
}

class CardClass: Object {
    @objc dynamic var team = ""
}

class MugClass: Object {
    @objc dynamic var liters = ""
}

and then a base realm object called Inventory Item Class that will represent them

class InvItemClass: Object {
    @objc dynamic var name = ""
    @objc dynamic var image = ""
    @objc dynamic var itemType = ""

    @objc dynamic var book: BookClass?
    @objc dynamic var mug: MugClass?
    @objc dynamic var card: CardClass?
}

then assume we want to store some books along with our mugs and cards (from the comments)

    let book2001 = BookClass()
    book2001.author = "Clarke"

    let bookIRobot = BookClass()
    bookIRobot.author = "Asimov"

    let item0 = InvItemClass()
    item0.name = "2001: A Space Odyssey"
    item0.image = "Pic of Hal"
    item0.itemType = "Book"
    item0.book = book2001

    let item1 = InvItemClass()
    item1.name = "I, Robot"
    item1.image = "Robot image"
    item1.itemType = "Book"
    item1.book = bookIRobot

    do {
        let realm = try Realm()

        try! realm.write {
            realm.add(item0)
            realm.add(item1)
        }

    } catch let error as NSError {
        print(error.localizedDescription)
    }

From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.

    do {
        let realm = try Realm()

        let items = realm.objects(InvItemClass.self)

        for item in items {
            switch item.itemType {
            case "Book":
                    let book = item.book
                    print(book?.author as! String)
            case "Mug":
                return
            default:
                return
            }
        }

    } catch let error as NSError {
        print(error.localizedDescription)
    }

As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks, Jay, for the thought that you've put into this. And I'm sorry if my original question wasn't clear enough. But your _solution_ is actually the problem I'm trying to solve. That's what I'm doing now. If, in the future, there might be some different object type (say, CompactDisc), that comes along, I don't want to go add another `case` statement. What I'm trying to do is create a new Realm Object, `ObjectReference` - with the sole purpose of keeping a reference to an object. If I can do that - then I can create a List of ObjectReferences. – Dan Morrow Oct 12 '17 at 03:30