Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheritance with Swift Realm, confusion

I have an issue about Inheritance with my Objects in Realm.

Could you please have a look a it. I have :

  • an Object Activity
  • an Object Sport which I want to be a subclass of Activity
  • an Object Seminar which I want to be a subclass of Activity

To make this happen I write, according to the documentation, the following code :

// Base Model
class Activity: Object {
      dynamic var id = ""
      dynamic var date = NSDate()   

     override static func primaryKey() -> String? {
        return "id"
     }
}

// Models composed with Activity
class Nutrition: Object {
    dynamic var activity: Activity? = nil
    dynamic var quantity = 0
}

  class Sport: Object {
    dynamic var activity: Activity? = nil
    dynamic var quantity = 0
    dynamic var duration = 0
}

Now I have an Model Category which I want it to hold the activities, doesn’t matter if it’s an Nutrition or Sport.

Here is my code :

class Categorie: Object {

    let activities = List<Activitie>()
    dynamic var categoryType: String = ""

    override static func primaryKey() -> String? {
        return "categoryType"
    }

}

Now I try to add a Nutrition object to my List<Activitie> by doing this :

let nutrition =  Nutrition(value: [ "activity": [ "date": NSDate(), "id": "0" ], "quantity": 12 ])

try! realm.write {
     realm.add(nutrition, update: true)
}

It doesn’t work because List<Activitie> expect an Activity Object and not a Nutrition Object. Where am I wrong ?

Thanks a lot for the help.

like image 786
xGoPox Avatar asked Dec 10 '22 12:12

xGoPox


2 Answers

You encountered one of the big problems of Realm : there is no complete polymorphism.

This github post gives a big highlight on what is possible or not, and a few possible solutions that you can use.

Quick quote from jpsim from the link above:

Inheritance in Realm at the moment gets you:

  • Class methods, instance methods and properties on parent classes are inherited in their child classes.
  • Methods and functions that take parent classes as arguments can operate on subclasses.

It does not get you:

  • Casting between polymorphic classes (subclass->subclass, subclass->parent, parent->subclass, etc.).
  • Querying on multiple classes simultaneously.
  • Multi-class container (RLMArray/List and RLMResults/Results).
like image 53
Yoam Farges Avatar answered Dec 29 '22 12:12

Yoam Farges


According to the article about type erased wrappers in swift and the #5 option I have ended up with something more flexible, here is my solution.

( please note that the solution #5 need to be updated for Swift 3, my solution is updated for Swift 3 )

My main Object Activity

class Activity: Object {
    dynamic var id = ""

    override static func primaryKey() -> String? {
        return "id"
    }
}

and my inheritance : Nutrition and Sport

class Nutrition: Activity { }

class Sport: Activity { }

The solution according to the solution #5 option : Using a type-erased wrapper for polymorphic relationships.

If you want to store an instance of any subclass of Activity, define a type-erased wrapper that stores the type's name and the primary key.

class AnyActivity: Object {
    dynamic var typeName: String = ""
    dynamic var primaryKey: String = ""

    // A list of all subclasses that this wrapper can store
    static let supportedClasses: [Activity.Type] = [
        Nutrition.self,
        Sport.self
    ]

    // Construct the type-erased activity from any supported subclass
    convenience init(_ activity: Activity) {
        self.init()
        typeName = String(describing: type(of: activity))
        guard let primaryKeyName = type(of: activity).primaryKey() else {
            fatalError("`\(typeName)` does not define a primary key")
        }
        guard let primaryKeyValue = activity.value(forKey: primaryKeyName) as? String else {
            fatalError("`\(typeName)`'s primary key `\(primaryKeyName)` is not a `String`")
        }
        primaryKey = primaryKeyValue
    }

    // Dictionary to lookup subclass type from its name
    static let methodLookup: [String : Activitie.Type] = {
        var dict: [String : Activity.Type] = [:]
        for method in supportedClasses {
            dict[String(describing: method)] = method
        }
        return dict
    }()

    // Use to access the *actual* Activitie value, using `as` to upcast
    var value: Activitie {
        guard let type = AnyActivity.methodLookup[typeName] else {
            fatalError("Unknown activity `\(typeName)`")
        }
        guard let value = try! Realm().object(ofType: type, forPrimaryKey: primaryKey) else {
            fatalError("`\(typeName)` with primary key `\(primaryKey)` does not exist")
        }
        return value
    }
}

Now, we can create a type that stores an AnyActivity!

class Category: Object {

    var categoryType: String = ""
    let activities = List<AnyActivity>()

    override static func primaryKey() -> String? {
        return "categoryType"
    }
}

and to store the data :

let nutrition = Nutrition(value : [ "id" : "a_primary_value"] )

let category = Category(value: ["categoryType" : "0"])

category.activities.append(AnyActivity(tree))

To read the data we want to check the activity method, use the value property on AnyActivity

for activity in activities {
    if let nutrition = activity.value as? Nutrition {
       // cool it's a nutrition
    } else if let sport = activity.value as? Sport {
       // cool it's a Sport   
    } else {
        fatalError("Unknown payment method")
    }
}
like image 27
xGoPox Avatar answered Dec 29 '22 10:12

xGoPox