与Swift领域的继承,混乱

Inheritance with Swift Realm, confusion

我对 Realm 中的对象的继承有疑问。

你能看看吗。我有:

为了做到这一点,我根据文档编写了以下代码:

// 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
}

现在我有一个模型 Category,我希望它能举办活动,不管它是 Nutrition 还是 Sport

这是我的代码:

class Categorie: Object {

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

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

}

现在我尝试通过这样做将 Nutrition 对象添加到我的 List<Activitie> 中:

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

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

它不起作用,因为 List<Activitie> 需要一个 Activity 对象而不是 Nutrition 对象。我哪里错了?

非常感谢您的帮助。

您遇到了 Realm 的一大问题:没有完全的多态性。

This github post 重点介绍了哪些是可能的,哪些是不可能的,以及一些您可以使用的可能的解决方案。

上面 link 中来自 jpsim 的快速引用:

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

在您的代码中 NutritionSport 不继承 Activity,它们继承 Object 并合成一个 Activity 实例。要继承 Activity,您应该

class Nutrition: Activity {
    dynamic var quantity = 0
}

我想你可能担心如果你这样做了,你的代码不再继承 Object。但事实并非如此。 Nutrition 仍然继承自 Object,因为 Object 继承自 Activity

他们的关系是营养:Activity:对象。

Owen 是正确的,关于 OO 原则,我也注意到,您并没有真正进行继承。

当一个对象使用另一个作为属性或属性时,它是关联,而不是继承。我也在审查 Realm 是否支持 Table/Object 级别的继承,就像 Java 对 Hibernate 所做的那样......但并不期望它。

这个框架虽然年轻但功能强大,足以让我避免使用 SQLite ...非常快速、易于使用并且更容易进行数据模型迁移!

正如 Yoam Farges 指出的那样,领域 不支持 :

Casting between polymorphic classes (subclass->subclass, subclass->parent, parent->subclass, etc.).

这就是我想要做的。

你们说我的 Inheritance 不是 Inheritance 是对的,但是正如你们在 Realm documentation 中看到的那样,这就是你们实现它的方式。

感谢我在 this github post 中获得的信息,我可以实现我想要的并且可以保持易读性。

为多态关系使用选项类型:

class PolyActivity: Object {
    dynamic var nutrition: Nutrition? = nil
    dynamic var sport: Sport? = nil
    dynamic var id = ""

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

创建我的主要对象Activity

class Activity: Object {
    dynamic var date = NSDate()
} 

并让我的 NutritionSport 对象正确继承到 Activity

class Nutrition: Activity {
    dynamic var quantity = 0
} 

class Sport: Activity {
    dynamic var quantity = 0
    dynamic var duration = 0
} 

我的 Category 对象现在可以容纳一个列表

class Categorie: Object {
    let activities = List<PolyActivity>()
    var categoryType: String = ""

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

这就是我创建 Nutrition 对象的方式:

let polyActivity = PolyActivity(value : [ "id": primaryKey ] )
poly.nutrition = Nutrition(value: [ "date": NSDate(), "quantity": 0, "duration": 0 ])

let category = Category(value: ["categoryType" : "0"])
category.activities.append(polyActivity)

并且只需使用可选绑定即可检索:

if let nutrition = category.activities[0].nutrition { }

如果你们有更好更清晰更简单的解决方案,请继续!

根据关于 type erased wrappers in swift and the #5 option 的文章,我最终得到了一些更灵活的东西,这是我的解决方案。

(请注意解决方案#5 需要为 Swift 3 更新,我的解决方案已更新为 Swift 3 )

我的主要对象Activity

class Activity: Object {
    dynamic var id = ""

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

和我的遗产:NutritionSport

class 营养:Activity { }

class 运动:Activity { }

根据解决方案#5 option的解决方案:为多态关系使用类型擦除的包装器。

如果您想存储 Activity 的任何子 class 的实例,定义一个类型擦除的包装器来存储类型的名称和主键。

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
    }
}

现在,我们可以创建一个类型来存储 AnyActivity!

class Category: Object {

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

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

并存储数据:

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

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

category.activities.append(AnyActivity(tree))

要读取数据我们要检查activity方法,在AnyActivity

上使用value属性
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")
    }
}