Swift realm : incorrect object written to db

Viewed 37

Here is a class to add/get data from Realm :

import Foundation
import RealmSwift

class WeatherDataOneDay {
    var temperature: Int = 0
    var temperatureDescription: String = ""
    var condition: Int = 0
    var city: String = ""
    var weatherIconName: String = ""
    var sunset: Double = 0
    var sunrise: Double = 0
    var humidity: Int = 0
    var windSpeed: Float = 0.0
    var date: Double = 0
    var feelsLike: Int = 0
    
    var clouds: Int = 0
    var lon: Float = 0.0
    var lat: Float = 0.0
    
    func printSelf()
    {
        print("temperature : \(temperature)")
        print("temperatureDescription : \(temperatureDescription)")
        print("condition : \(condition)")
        print("city : \(city)")
        print("weatherIconName : \(weatherIconName)")
        print("sunset : \(sunset)")
        print("sunrise : \(sunrise)")
        print("humidity : \(humidity)")
        print("windSpeed : \(windSpeed)")
        print("date : \(date)")
        print("feelsLike : \(feelsLike)")
        print("clouds : \(clouds)")
        print("lon : \(lon)")
        print("lat : \(lat)")
    }
}

@objcMembers class WeatherDataOneDayCached: Object {
    dynamic var poi: String?
    
    dynamic var temperature: Int?
    dynamic var temperatureDescription: String?
    dynamic var condition: Int?
    dynamic var city: String?
    dynamic var weatherIconName: String?
    dynamic var sunset: Double?
    dynamic var sunrise: Double?
    dynamic var humidity: Int?
    dynamic var windSpeed: Float?
    dynamic var date: Double?
    dynamic var feelsLike: Int?
    dynamic var clouds: Int?
    dynamic var lon: Float?
    dynamic var lat: Float?

    override static func primaryKey() -> String? {
        return "poi"
    }
    
    func printSelf()
    {
        print("temperature : \(temperature)")
        print("temperatureDescription : \(temperatureDescription)")
        print("condition : \(condition)")
        print("city : \(city)")
        print("weatherIconName : \(weatherIconName)")
        print("sunset : \(sunset)")
        print("sunrise : \(sunrise)")
        print("humidity : \(humidity)")
        print("windSpeed : \(windSpeed)")
        print("date : \(date)")
        print("feelsLike : \(feelsLike)")
        print("clouds : \(clouds)")
        print("lon : \(lon)")
        print("lat : \(lat)")
    }

}

class WeatherDataOneDayDbProvider {
    private var realm: Realm?
    
    init() {
        var config = Realm.Configuration()
        config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("weather_data_one_day.realm")
        realm = try? Realm(configuration: config)        
    }
            
    func getOneDayWeatherDataForPoi(poi : String) -> WeatherDataOneDay? {
        let weatherData = realm?.object(ofType: WeatherDataOneDayCached.self, forPrimaryKey: poi)
        if let weatherData = weatherData {
            let result = WeatherDataOneDay()
            result.temperature = weatherData.temperature ?? 0
            result.temperatureDescription = weatherData.temperatureDescription ?? ""
            result.condition = weatherData.condition ?? 0
            result.city = weatherData.city ?? ""
            result.weatherIconName = weatherData.weatherIconName ?? ""
            result.sunset = weatherData.sunset ?? 0.0
            result.sunrise = weatherData.sunrise ?? 0.0
            result.humidity = weatherData.humidity ?? 0
            result.windSpeed = weatherData.windSpeed ?? 0.0
            result.date = weatherData.date ?? 0.0
            result.feelsLike = weatherData.feelsLike ?? 0
            result.clouds = weatherData.clouds ?? 0
            result.lon = weatherData.lon ?? 0.0
            result.lat = weatherData.lat ?? 0.0
            
            return result
        } else {
            return nil
        }
    }
    
    func addOneDayWeatherDataForPoi(poi : String, weatherData: WeatherDataOneDay) {
        let cachedWeatherData = WeatherDataOneDayCached()
        cachedWeatherData.poi = poi
        
        cachedWeatherData.temperature = weatherData.temperature
        cachedWeatherData.temperatureDescription = weatherData.temperatureDescription
        cachedWeatherData.condition = weatherData.condition
        cachedWeatherData.city = weatherData.city
        cachedWeatherData.weatherIconName = weatherData.weatherIconName
        cachedWeatherData.sunset = weatherData.sunset
        cachedWeatherData.sunrise = weatherData.sunrise
        cachedWeatherData.humidity = weatherData.humidity
        cachedWeatherData.windSpeed = weatherData.windSpeed
        cachedWeatherData.date = weatherData.date
        cachedWeatherData.feelsLike = weatherData.feelsLike
        cachedWeatherData.clouds = weatherData.clouds
        cachedWeatherData.lon = weatherData.lon
        cachedWeatherData.lat = weatherData.lat
                
        if isWeatherDataExist(poi: poi) {
            try? realm?.write {
                realm?.add(cachedWeatherData, update: .all)
            }
        } else {
            try? realm?.write {
                realm?.add(cachedWeatherData)
            }
        }
    }
        
    func isWeatherDataExist(poi: String) -> Bool {
        return realm?.object(ofType: WeatherDataOneDayCached.self, forPrimaryKey: poi) != nil
    }
}

class WeatherDataOneDayDB {
    private let dbDataProvider : WeatherDataOneDayDbProvider = WeatherDataOneDayDbProvider()
    
    static var shared: WeatherDataOneDayDB = {
        let instance = WeatherDataOneDayDB()
        return instance
    }()

    private init() {}
    
    func getOneDayWeatherDataForPoi(poi : String) -> WeatherDataOneDay? {
        return dbDataProvider.getOneDayWeatherDataForPoi(poi: poi)
    }
    
    func addOneDayWeatherDataForPoi(poi : String, weatherData: WeatherDataOneDay) {
        return dbDataProvider.addOneDayWeatherDataForPoi(poi: poi, weatherData: weatherData)
    }
}

And this is a test snippet :

let test = WeatherDataOneDay()
test.temperature = 26
test.temperatureDescription = "clear sky"
test.condition = 800
test.city = "Globe"
test.weatherIconName = ""
test.sunset = 1644517064.0
test.sunrise = 1644473452.0
test.humidity = 80
test.windSpeed = 2.4
test.date = 1644521917.0
test.feelsLike = 29
test.clouds = 0
test.lon = 0.0
test.lat = 0.0
print("!!!!!!!DEBUUUUUUUUG!!!!!!!!!!!")
let db = WeatherDataOneDayDB.shared
db.addOneDayWeatherDataForPoi(poi: "Москва",
                              weatherData: test)

if let obj = db.getOneDayWeatherDataForPoi(poi: "Москва") {
    obj.printSelf()
}

I see that the object is not completely written to DB - only few members are fine and all the rest are nil. This is what I receive :

temperature : 0
temperatureDescription : clear sky
condition : 0
city : Globe
weatherIconName : 
sunset : 0.0
sunrise : 0.0 ....

I access the DB from main thread only. What am I missing?

2 Answers

First of all, avoid optional primary key, it makes no sense. And I'd recommend to use modern syntax for object definition:

class WeatherDataOneDayCached: Object {
    @Persisted(primaryKey: true) var poi: String
    @Persisted var temperature: Int?
    ...
}

This alone will solve the issue. Also you can have more Swifty code for db Provider:

class WeatherDataOneDayDbProvider {
private var realm: Realm // No need to have it optional if you can't continue without one

init() {
    var config = Realm.Configuration()
    ...
    realm = try! Realm(configuration: config) // Just add exceptions handling or ! for prototyping
}
        
func getOneDayWeatherDataForPoi(poi : String) -> WeatherDataOneDay? {
    // Save some lines here too
    guard let weatherData = realm.object(ofType: WeatherDataOneDayCached.self, forPrimaryKey: poi) else {
        return nil
    }
    let result = WeatherDataOneDay()
    result.temperature = weatherData.temperature ?? 0
    ...
    return result
}

func addOneDayWeatherDataForPoi(poi : String, weatherData: WeatherDataOneDay) {
    try? realm.write {
        // get available object or create new in a single line
        let cachedWeatherData = realm.object(ofType: WeatherDataOneDayCached.self, forPrimaryKey: poi) ??
        realm.create(WeatherDataOneDayCached.self, value: ["poi": poi])

        cachedWeatherData.temperature = weatherData.temperature
        ...
    }
}

}

Instead of long definitions of printSelf() you can just write dump(object) right in your code

Here is the solution for my case (seems to be an issue with optional usage somehow) :

class WeatherDataOneDayCached: Object {
    @objc dynamic var poi: String = ""
    
    @objc dynamic var temperature: Int = 0
    @objc dynamic var temperatureDescription: String = ""
    @objc dynamic var condition: Int = 0
    @objc dynamic var city: String = ""
    @objc dynamic var weatherIconName: String = ""
    @objc dynamic var sunset: Double = 0
    @objc dynamic var sunrise: Double = 0
    @objc dynamic var humidity: Int = 0
    @objc dynamic var windSpeed: Float = 0
    @objc dynamic var date: Double = 0
    @objc dynamic var feelsLike: Int = 0
    @objc dynamic var clouds: Int = 0
    @objc dynamic var lon: Float = 0
    @objc dynamic var lat: Float = 0

    override static func primaryKey() -> String? {
        return "poi"
    }
    
    func printSelf()
    {
        print("temperature : \(temperature)")
        print("temperatureDescription : \(temperatureDescription)")
        print("condition : \(condition)")
        print("city : \(city)")
        print("weatherIconName : \(weatherIconName)")
        print("sunset : \(sunset)")
        print("sunrise : \(sunrise)")
        print("humidity : \(humidity)")
        print("windSpeed : \(windSpeed)")
        print("date : \(date)")
        print("feelsLike : \(feelsLike)")
        print("clouds : \(clouds)")
        print("lon : \(lon)")
        print("lat : \(lat)")
    }

}
Related