0

I have a project which uses ObjectMapper for JSON serialization & deserialization and Realm to cache data locally. I get data from Rest API and save them into realm database properly I have checked with RealmStudio. The problem is when I retrieve data from database a property which is Object type returns nil! This is my class and foodFact is Object type which is nil when get FoodRecipe data from database.Any Ideas?

 import RealmSwift
 import ObjectMapper


 typealias Syncable = Object & Mappable


class FoodFact: Syncable {
  @objc dynamic var objectId :String = ""
  @objc dynamic var calcium: String = ""
  @objc dynamic var iron: String = ""
  @objc dynamic var fiber: String = ""
  @objc dynamic var fattyTransAcide: String = ""


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

 required convenience init?(map: Map) { self.init() }


 func mapping(map: Map) {
    self.objectId <- map["objectId"]
    self.calcium <- map["calcium"]
    self.iron <- map["iron"]
    self.fiber <- map["fiber"]
    self.fattyTransAcide <- map["fattyTransAcide"]
  }
}


class FoodRecipe: Syncable {
  @objc dynamic var objectId :String = ""
  @objc dynamic var title :String = ""
  var ingredient = List<FoodRecipeIngredient>()
  @objc dynamic var foodFact :FoodFact? //returns nil when retrieving    data


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

required convenience init?(map: Map) { self.init() }

func mapping(map: Map) {
    objectId <- map["objectId"]
    title <- map["title"]
    ingredient <- (map["ingredients"],ArrayTransform<FoodRecipeIngredient>())
    foodFact <- map["foodFact"]
   }
 }

This is code I use for save & retrieve data :

import Foundation
import RealmSwift

protocol BaseDao {
  associatedtype T: Object

  func save(list:[T])
  func save(object:T)
  func get() -> [T]?
  func delete(list:[T])
  func deleteAll()
}

extension BaseDao {
  func deleteAll(){
    PersistenceManager().deleteDatabase()
  }
}


class PersistenceManager {

  let realm = try! Realm()
  var notificationToken = NotificationToken()


  func deleteObjects(objs:[Object]) {
    do {
        try realm.write({
            realm.delete(objs)
        })
    } catch let error {
        assertionFailure(error.localizedDescription)
    }
}

func saveObjects(objs: [Object]) {
    print("path for realm \(String(describing: realm.configuration.fileURL))")
    do {
        try realm.write({
            realm.add(objs, update: true)
        })
    } catch let error {
        assertionFailure(error.localizedDescription)
    }

}

func saveObject(obj: Object) {
    print("path for realm \(String(describing: realm.configuration.fileURL))")
    do {
        try realm.write({
            realm.add(obj, update: true)
        })
    } catch let error {
        assertionFailure(error.localizedDescription)
    }

}

func getObjects(type: Object.Type) -> Results<Object>? {
    return realm.objects(type)
}

func deleteDatabase() {
    do {
        try realm.write({
        realm.deleteAll()
        })
    } catch let error {
        assertionFailure(error.localizedDescription)
    }
  }
 }


class FoodRecipeLocalPersistence :BaseDao {
typealias T = FoodRecipe

let persistenceManager :PersistenceManager

init(persistenceManager :PersistenceManager = PersistenceManager()) {
    self.persistenceManager = persistenceManager
}

func save(list: [T]) {
    self.persistenceManager.saveObjects(objs: list)
}

func save(object: T) {
    self.persistenceManager.saveObject(obj: object)
}

func get() -> [T]? {
    guard let data = persistenceManager.getObjects(type: T.self) else {
        return nil
    }
    return Array(data) as? [T]
}

  func delete(list: [T]) {
    self.persistenceManager.deleteObjects(objs: list)
  }
}

Update 1: After Further review I have realized I can retrieve FoodRecipe with all properties in FoodRecipePersistence but in my ViewModel FoodFact is nil! I am using RxSwift in my ViewModel below is code snippet:

   Observable.combineLatest(selectedCategory, currentCategory, resultSelector: { (selectedCategory, currentCategory) in
        switch currentCategory {
        case .Food:
            let category = selectedCategory as? FoodRecipeCategory

            return foodRecipeLocalPersistence.get()?.filter{ $0.categoryId == category?.objectId }.map{$0} ?? []
        case .Sport:
            let category = selectedCategory as? ExerciseInstructionCategory
            return exerciseInstructionLocalPersistence.get()?.filter{ $0.categoryId == category?.objectId }.map{$0} ?? []
        }
    }).asObservable().bind(to: self.shownList).disposed(by: disposeBag)

update 2: When I use

po foodRecipe.foodFact

foodFact is printed on console with all data and properties but when I put mouse on foodRecipe.foodFact inside the editor it shows nil!!!

FaFa
  • 59
  • 10
  • 1
    You may need to show how you are storing and retrieving the data. I don't see anything wrong with your classes. – Don May 08 '19 at 21:46
  • I add codes which I use for save and retrieving the data – FaFa May 08 '19 at 22:27
  • So, you created a FoodFact, added it to Realm, then set `foodRecipe.foodFact = foodFact` and added foodRecipe to Realm? Unless you forgot a step, I don't know what else to try. It's strange RealmStudio displays the correct data, but it still returns nil. – Don May 08 '19 at 23:18

2 Answers2

1

This is a shot in the dark. The code to actually setup the classes and populate them wasn't included in the question so what I did is took your code, stripped it down a bit and ran a couple of tests. With my code, the data is both written and read correctly.

Here are the stripped down classes

class FoodFact: Object {
    @objc dynamic var objectId :String = ""
    @objc dynamic var calcium: String = ""
    @objc dynamic var iron: String = ""
    @objc dynamic var fiber: String = ""
    @objc dynamic var fattyTransAcide: String = ""

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

class FoodRecipe: Object {
    @objc dynamic var objectId :String = ""
    @objc dynamic var title :String = ""
    @objc dynamic var foodFact :FoodFact? //returns nil when retrieving    data

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

class PersistenceManager {
    let realm = try! Realm()
    var notificationToken = NotificationToken()

    func deleteObjects(objs:[Object]) {
        do {
            try realm.write({
                realm.delete(objs)
            })
        } catch let error {
            assertionFailure(error.localizedDescription)
        }
    }

    func saveObject(obj: Object) {
        print("path for realm \(String(describing: realm.configuration.fileURL))")
        do {
            try realm.write({
                realm.add(obj, update: true)
            })
        } catch let error {
            assertionFailure(error.localizedDescription)
        }
    }

    func getObjects(type: Object.Type) -> Results<Object>? {
        return realm.objects(type)
    }
}

I have two buttons in the UI, one that creates the data and stores it in Realm and the other that reads it back.

Creating data

func doButtonAction1() {
    let ff0 = FoodFact()
    ff0.calcium = "lots of calcium"

    let fr0 = FoodRecipe()
    fr0.title = "Milk"
    fr0.foodFact = ff0

    let pm = PersistenceManager()
    pm.saveObject(obj: fr0)
}

and then reading it back

func doButtonAction2() {
    let pm = PersistenceManager()
    let recipeResults = pm.getObjects(type: FoodRecipe.self)

    if let allRecipies = recipeResults {
        for recipe in allRecipies {
            print(recipe)
        }
    }
}

when clicking button 1, the path prints to the console and then clicking button 2 I see this, which shows the data has been saved and is reading correctly.

FoodRecipe {
    objectId = ;
    title = Milk;
    foodFact = FoodFact {
        objectId = ;
        calcium = lots of calcium;
        iron = ;
        fiber = ;
        fattyTransAcide = ;
    };
}

So we know the core code to read and write data is working so that would leave the issue being caused by either the code I stripped out (which wasnt much) or something else entirely. Hopefully this will provide some direction for further troubleshooting.

Jay
  • 34,438
  • 18
  • 52
  • 81
0

After searching I have found out This problem was happened when I used Mirror to get Realm Object properties. To solve the problem first of all Object should be detached from Realm. Using below code helps you detach Realm Objects and second you can use Mirror with detached object to get properties.

protocol DetachableObject: AnyObject {
    func detached() -> Self
}

extension Object: DetachableObject {

    func detached() -> Self {
        let detached = type(of: self).init()
        for property in objectSchema.properties {
            guard let value = value(forKey: property.name) else { continue }

            if property.isArray == true {
                //Realm List property support
                let detachable = value as? DetachableObject
                detached.setValue(detachable?.detached(), forKey: property.name)
            } else if property.type == .object {
                //Realm Object property support
                let detachable = value as? DetachableObject
                detached.setValue(detachable?.detached(), forKey: property.name)
            } else {
                detached.setValue(value, forKey: property.name)
            }
        }
        return detached
    }
}

extension List: DetachableObject {
    func detached() -> List<Element> {
        let result = List<Element>()

        forEach {
            if let detachable = $0 as? DetachableObject {
                let detached = detachable.detached() as! Element
                result.append(detached)
            } else {
                result.append($0) //Primtives are pass by value; don't need to recreate
            }
        }

        return result
    }

    func toArray() -> [Element] {
        return Array(self.detached())
    }
}

extension Results {
    func toArray() -> [Element] {
        let result = List<Element>()

        forEach {
            result.append($0)
        }

        return Array(result.detached())
    }
}
FaFa
  • 59
  • 10