【问题标题】:Inheritance with Swift Realm, confusionSwift Realm 的继承,困惑
【发布时间】:2017-03-15 12:10:06
【问题描述】:

我的 Realm 中的对象存在继承问题。

你能看一下吗。我有:

  • 一个对象Activity
  • 一个对象Sport,我想成为Activity的子类
  • 一个对象Seminar,我想成为Activity的子类

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

// 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&lt;Activitie&gt;

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

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

它不起作用,因为 List&lt;Activitie&gt; 期望的是 Activity 对象而不是 Nutrition 对象。我哪里错了?

非常感谢您的帮助。

【问题讨论】:

    标签: ios swift inheritance realm one-to-many


    【解决方案1】:

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

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

    来自 jpsim 的快速引用来自上面的链接:

    Realm 中的继承让你:

    • 父类上的类方法、实例方法和属性是 在他们的子类中继承。
    • 采用的方法和函数 作为参数的父类可以对子类进行操作。

    它没有得到你:

    • 多态类(子类->子类, 子类->父类、父类->子类等)。
    • 同时查询多个类。
    • 多类容器(RLMArray/List 和 RLMResults/Results)。

    【讨论】:

    • 感谢您的回复,我有时间就去看看,(下周星期一!)干杯!
    【解决方案2】:

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

    (请注意解决方案#5 需要针对 Swift 3 进行更新,我的解决方案针对 Swift 3 进行了更新)

    我的主要对象Activity

    class Activity: Object {
        dynamic var id = ""
    
        override static func primaryKey() -> String? {
            return "id"
        }
    }
    

    还有我的继承:NutritionSport

    类营养:活动{}

    类运动:活动{}

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

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

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

    要读取我们要检查活动方法的数据,请使用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")
        }
    }
    

    【讨论】:

    • 忘记从示例中删除 fatalError("Unknown payment method")。但认真的好工作。几年前我不得不使用这种方法。不过,如果使用 Realm 拥有更好的多态性和模型继承,那就太好了。
    【解决方案3】:

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

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

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

    【讨论】:

    • 感谢您的回复,我有时间就去看看,(下周星期一!)干杯!
    • 嘿,我明白了,但是查看 Realm 文档继承不能那样做。请看:realm.io/docs/swift/latest/#model-inheritance
    • 可以肯定...继承有其自身的风险和回报。然而,很多时候,它在代码中可能比在框架中做得更好。数据继承并不总是需要考虑的事情,因为从长远来看,它可能会导致问题。总是需要小心行事。
    【解决方案4】:

    在您的代码中NutritionSport 不继承Activity,它们继承Object 并组合Activity 实例。要继承Activity,你应该这样做

    class Nutrition: Activity {
        dynamic var quantity = 0
    }
    

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

    它们的关系是营养:活动:对象。

    【讨论】:

    • 感谢您的回复,我有时间就去看看,(下周星期一!)干杯!
    • 嘿,我明白,但是查看 Realm 文档继承不能那样做。请看:realm.io/docs/swift/latest/#model-inheritance
    • 如前所述,Realm 不支持这种多态性。如果您尝试使用它,您将在尝试持久化子类时立即收到运行时 RLMException。这个code sample post on GitHub 详细介绍了使用抽象类/多态性的解决方法/最佳实践。 (也请参阅下面的答案)。
    【解决方案5】:

    正如 Yoam Farges 指出的那样,Realm 不支持

    多态类之间的转换(子类->子类, 子类->父类、父类->子类等)。

    这是我想要做的。

    你们说我的继承不是继承是对的,但正如您在 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 { }
    

    如果你们有一个更好更清晰更简单的解决方案,请去!

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-18
    • 2011-12-18
    • 1970-01-01
    • 1970-01-01
    • 2019-11-07
    • 2019-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多