在设计 Core Data 模型时,很自然地会想到使用继承来抽取共享的属性,以减少重复并保持代码的整洁。例如,我们可能会想为每个模型添加 createdateupdatedate 字段以跟踪它们的创建和更新时间。下面我们将探讨在 Core Data 中如何实现继承,并指出使用继承可能导致的一个主要问题。

实现继承

  1. 创建基类实体: 在你的 xcdatamodeld 文件中创建一个名为 BaseEntity 的新实体,并在其中定义 createdateupdatedate 两个属性,将它们的类型设置为 Date.

  2. 设置其他实体的父类: 选择你想要继承 createdateupdatedate 属性的每个实体,并在“父类”设置中选择 BaseEntity.

  3. 生成 NSManagedObject 子类: 为你的实体生成 NSManagedObject 子类。现在 BaseEntity 以及从它继承的所有其他实体都应包括 createdateupdatedate 属性。

  4. 覆盖 awakeFromInsertwillSave 方法 (可选): 在 BaseEntity 中覆盖 awakeFromInsertwillSave 方法以确保 createdateupdatedate 在适当的时候被设置。

import Foundation
import CoreData

@objc(BaseEntity)
public class BaseEntity: NSManagedObject {

    @NSManaged public var createdate: Date?
    @NSManaged public var updatedate: Date?

    public override func awakeFromInsert() {
        super.awakeFromInsert()
        self.createdate = Date()
    }

    // 不能在这里更新 updatedate
    //public override func willSave() {
    //    super.willSave()
    //    self.updatedate = Date()
    //}

    public override func didChangeValue(forKey key: String) {
        super.didChangeValue(forKey: key)
        
        if key != #keyPath(createdate) {
            createdate = Date()
        }
    }
}

继承带来的问题

虽然继承在许多情况下都是有用的,但在 Core Data 中使用它可能会导致一个不太理想的结果:所有继承自同一个父类的实体都会被存储在同一个表中,这种行为被称为“单表继承”(Single Table Inheritance)。虽然这种机制有助于保持数据模型的简单和高效,但可能不适用于所有情况,特别是当我们想要为每个模型创建一个单独的表时。

替代方案

为了避免“单表继承”而又能够实现通用字段的需求,我们可以考虑以下两种替代方案:

  1. 代码重用: 创建一个协议或扩展来重用 createdateupdatedate 的逻辑,而不是通过继承来实现。例如,我们可以创建一个 TimeStamped 协议,并为 NSManagedObject 添加一个扩展来实现这个协议。
protocol TimeStamped {
    var createdate: Date? { get set }
    var updatedate: Date? { get set }
}

extension NSManagedObject: TimeStamped {
    @NSManaged public var createdate: Date?
    @NSManaged public var updatedate: Date?

    func updateTimestamps() {
        if self.isInserted {
            self.createdate = Date()
        }
        if self.isUpdated {
            self.updatedate = Date()
        }
    }
}

// 在保存操作之前调用 updateTimestamps
viewContext.observe(\.hasChanges) { context, change in
    if context.hasChanges {
        for object in context.insertedObjects.union(context.updatedObjects) {
            (object as? TimeStamped)?.updateTimestamps()
        }
    }
}
  1. 手动添加属性: 对于每个实体,手动添加 createdateupdatedate 属性。虽然这可能会增加一些重复的工作,但它可以确保每个实体都有自己的表。

通过深入了解 Core Data 中继承的工作机制以及可能出现的问题,我们可以更加明智地决定如何设计我们的数据模型,以满足应用的需求同时保持代码的整洁和可维护性。