在领域中创建对对象的引用的正确方法

Posted

技术标签:

【中文标题】在领域中创建对对象的引用的正确方法【英文标题】:Correct way of creating reference to object in Realm 【发布时间】:2022-01-07 08:04:18 【问题描述】:

我创建了一个健身应用,并使用 Realm 作为本地数据库。在第一次启动期间,我想用包含初始数据(练习名称、设备、肌肉参与等)的领域文件替换默认领域。这个初始数据将来不会改变。我想知道是否存在某种方式可以帮助我在主类中创建对另一个较小类的引用。我需要这个来更轻松地过滤和获取数据。

这是我的主要领域类

     class Exercise: Object 
        @Persisted var exerciseID: Int = 0
        @Persisted var name: String = ""
        @Persisted var category: Int
        @Persisted var equipment: String
        @Persisted var instruction: String
        @Persisted var muscle: String
        @Persisted var gif: String?
        @Persisted var image: String? = nil
        
        convenience init(name: String, category: Int, equipment: String, instruction: String, muscle: String, gif: String?, image: String?) 
            self.init()
            self.name = name
            self.category = category
            self.equipment = equipment
            self.instruction = instruction
            self.muscle = muscle
            self.gif = gif
            self.image = image
        
        
        override static func primaryKey() -> String? 
            return "exerciseID"
        
    

当我想要获得所有练习和指定的设备和肌肉时,检索这些数据确实需要大量代码,尤其是当字符串包含对对象的很少引用时。

 var exercises = [Exercise]()
    var equipments = [Equipment]()

    func getAllExercises() 

            let data = RealmService.shared.realm.objects(Exercise.self)
            exercises = data.compactMap($0)

            let equipment = exercises.compactMap($0.equipment)
            
            for eq in exercises.compactMap($0.equipment) 
                let numberOfEquipment = eq.components(separatedBy: ",")       
                
                for number in numberOfEquipment 
                    
                    guard let intNumber = Int(number) else  return 
                    guard let finalEquipment = RealmService.shared.realm.object(ofType: Equipment.self, forPrimaryKey: intNumber) else  return 
                    equipments.append(finalEquipment)
                
            

也许更好的选择是只插入值而不是对象引用?

【问题讨论】:

听起来您想将 Realm 与您的应用程序捆绑在一起。换句话说,您有预定义的数据(不会更改)并希望它在应用程序首次运行时可用。如果正确,请参阅Bundle a Realm 的此答案,然后查看 Realm 文档 Bundle a Realm File 就查询而言,我们需要更清晰的细节。您具体要查询什么?例如我想查询我的锻炼对象以查找所有name 等于“卧推”的对象 - 告诉我们您想要什么数据。哦,使用 Realm 避免使用 Swift 高级函数(如 compactMap)可能是一个好主意——尤其是在您拥有大型数据集的情况下。领域对象是延迟加载的,因此成千上万的对象几乎不占用空间。但是,一旦使用了高级函数,所有数据都会被加载,并且可能会压倒设备内存(并使其变慢)。 我想创建从运动班到小班(类别、设备和肌肉)的参考。因为目前我想得到例如我需要做的装备:1)获取Exercise,2)获取装备编号,3)在装备类中查询具体的装备编号,最终得到。但我想要这样的东西:1)锻炼,2)在特定位置使用对较小班级的参考并得到它。 为什么不为类别、设备和肌肉创建一个类并在您的运动对象中使用它们?我对“获取设备”的含义有些困惑-如果您想知道卧推需要什么设备,则需要先查找卧推,对吗?例如卧推需要杠铃,但其他类型的练习也需要。 我想过,但是我从 csv 设置了我的初始领域文件,我不知道如何在 csv 文件中设置对领域类的引用。 【参考方案1】:

您需要设置一对多关系以利用更快的查询和延迟加载。

我已经简化了模型,但神奇之处在于 equipmentObjects 属性:

class Exercise: Object 
    @Persisted(primaryKey: true) var exerciseID = 0
    @Persisted var name: String = ""
    @Persisted var equipment: String
    @Persisted var equipmentObjects: List<Equipment>

    convenience init(exerciseID: Int, name: String, equipment: String) 
        self.init()
        self.exerciseID = exerciseID
        self.name = name
        self.equipment = equipment
    


class Equipment: Object 
    @Persisted(primaryKey: true) var equipmentID = 0
    @Persisted var equipment: String = ""

    convenience init(equipmentID: Int, equipment: String) 
        self.init()
        self.equipmentID = equipmentID
        self.equipment = equipment
    

您可以继续使用 csv 文件初始化领域。但是当应用程序启动时,您会想要继续建立运动、设备和肌肉之间的关系。您应该只执行一次。

在这里,我创建了一个小实用程序来链接领域对象。注意它是如何使用 UserDefaults 来检查关系是否已经建立的。它还在指定队列上建立关系。您可能希望传入后台队列而不是主队列,这样 UI 就不会锁定。

struct RealmRelationshipBuilder 
    let configuration: Realm.Configuration
    let userDefaults: UserDefaults
    let queue: DispatchQueue

    func buildRelationshipsIfNeeded(completion: @escaping() -> Void) 
        guard userDefaults.didBuildRealmRelationships == false else  return completion() 

        queue.async 
            autoreleasepool 
                defer  completion() 
                do 
                    let realm = try Realm(configuration: configuration)
                    try realm.write 

                        realm.objects(Exercise.self).forEach  exercise in
                            let equipment = exercise
                                .equipment
                                .components(separatedBy: ",")
                                .compactMap(Int.init)
                                .compactMap  realm.object(ofType: Equipment.self, forPrimaryKey: $0) 
                            exercise.equipmentObjects.append(objectsIn: equipment)
                        
                    
                 catch 
                    print("RealmRelationshipBuilder error: \(error)")
                
                userDefaults.didBuildRealmRelationships = true
            
        
    


extension UserDefaults 
    enum Key 
        static let didBuildRealmRelationships = "didBuildRealmRelationshipsKey"
    

    var didBuildRealmRelationships: Bool 
        get  bool(forKey: Key.didBuildRealmRelationships) 
        set  set(newValue, forKey: Key.didBuildRealmRelationships) 
    

那么这里要测试builder是一个小测试用例。但实际上,您可能希望在后台建立关系时向用户显示状态指示器。

enum InitialData 
    static let exercises: [Exercise] = 
        [
            Exercise(exerciseID: 1, name: "Bench press", equipment: "1,3,5"),
            Exercise(exerciseID: 2, name: "Butterfly", equipment: "6"),
        ]
    ()

    static let equipment: [Equipment] = 
        [
            Equipment(equipmentID: 1, equipment: "Barbell"),
            Equipment(equipmentID: 2, equipment: "Bench"),
            Equipment(equipmentID: 3, equipment: "Bodyweight"),
            Equipment(equipmentID: 4, equipment: "Cable"),
            Equipment(equipmentID: 5, equipment: "Not sure"),
            Equipment(equipmentID: 6, equipment: "Unknown"),
        ]
    ()


class RealmExerciseTests: XCTestCase 

    let realmConfiguration = Realm.Configuration.defaultConfiguration

    override func setUpWithError() throws 
        let realm = try Realm(configuration: realmConfiguration)
        try realm.write 
            realm.deleteAll()
            realm.add(InitialData.exercises)
            realm.add(InitialData.equipment)
        
    

    func testInitialize() throws 

        let relationshipBuilder = RealmRelationshipBuilder(
            configuration: realmConfiguration,
            userDefaults: .init(suiteName: UUID().uuidString) ?? .standard,
            queue: DispatchQueue(label: "realm.init.background")
        )

        let expectation = expectation(description: "realm.init")

        relationshipBuilder.buildRelationshipsIfNeeded 
            expectation.fulfill()
        

        wait(for: [expectation], timeout: 2.0)

        let realm = try Realm(configuration: realmConfiguration)
        realm.refresh()

        guard let exercise1 = realm.object(ofType: Exercise.self, forPrimaryKey: 1) else 
            return XCTFail("Missing exercise with primary key 1")
        

        guard let exercise2 = realm.object(ofType: Exercise.self, forPrimaryKey: 2) else 
            return XCTFail("Missing exercise with primary key 2")
        
        XCTAssertEqual(exercise1.equipmentObjects.count, 3)
        XCTAssertEqual(exercise2.equipmentObjects.count, 1)
    

【讨论】:

一个非常好的答案,但可能不是实际问题的答案。看来 OP 正在询问如何从原始文本文件而不是在 Realm 中创建关系。似乎他们知道如何将对象联系在一起,如this answer所示,但不知道如何从原始数据中建立对象关系——坦率地说,在不知道原始数据是什么样子的情况下,这个答案可能不准确。 (或者,它可能是正确的!)。希望他们能澄清问题,以便我们支持这个答案

以上是关于在领域中创建对对象的引用的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ActiveRecords 中创建对 Ruby 中对象的引用?

在Firestore中创建对Cloud Storage文档的引用

在 C++ 中创建对三元运算符结果的 const 引用是不是安全?

无法在 XAML 中创建对其他项目的 xmlns 引用

如何在数据库项目中创建对动态数据库名称的引用?

这是处理领域中两个相似对象的正确方法吗?