Не удается обновить содержимое одного RealmSwift.List другим

Есть классы Setting, Device и Attributes:

class SettingType: CustomStringConvertible {
    let key: Attributes.Keys

    init(key: Attributes.Keys) {
        self.key = key
    }

    var name: String? {
        return self.key.description
    }

    var description: String {
        return "no value in SettingsType"
    }
}

class Setting<T>: SettingType {
    var value: T

    init(key: Attributes.Keys, value: T) {
        self.value = value
        super.init(key: key)
    }

    override var description: String {
        guard let descriptible = value as? CustomStringConvertible else {
            return "value has not description"
        }
        return descriptible.description
    }
}

class Device: Object, Mappable, Extensible {
    override static func primaryKey() -> String? {
        return "id"
    }

    var id = RealmOptional<Int>()
    dynamic var attributes: Attributes!
    dynamic var name: String!
    dynamic var phone: String!
    dynamic var uniqueId: String!
    dynamic var category: String?
    dynamic var contact: String?
    let geofenceIds = RealmSwift.List<Integer>()
    let groupId = RealmOptional<Int>()
    dynamic var lastUpdate: String?
    dynamic var model: String?
    let positionId = RealmOptional<Int>()
    dynamic var status: String?
    dynamic var photo: Data?

    convenience init(id: Int? = nil, attributes: Attributes = Attributes.empty, name: String, phone: String, uniqueId: String) {
        self.init()
        self.id.value = id
        self.attributes = attributes
        self.name = name
        self.phone = phone
        self.uniqueId = uniqueId
    }

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

    func mapping(map: Map) {
        id.value            <- map["id"]
        attributes          <- map["attributes"]
        name                <- map["name"]
        phone               <- map["phone"]
        uniqueId            <- map["uniqueId"]
        category            <- map["category"]
        contact             <- map["contact"]
        geofenceIds         <- map["geofenceIds"]
        groupId.value       <- map["groupId"]
        lastUpdate          <- map["lastUpdate"]
        model               <- map["model"]
        positionId.value    <- map["positionId"]
        status              <- map["status"]
    }
}

extension Device {
    var settings: [SettingType] {
        get {
            var result = [SettingType]()
            for key in Attributes.Keys.settings {
                switch key {
                case .showOnMap         :
                    result.append(Setting<Bool>(key: key, value: self.attributes.showOnMap))
                case .observers         :
                    result.append(Setting<RealmSwift.List<Observer>>(key: key, value: self.attributes.listObservers))
                case .trackingMode      :
                    guard let trackMode = self.attributes.trackingMode else {
                        result.append(Setting<TrackMode>.defaultValue(for: key)!)
                        break
                    }
                    result.append(Setting<TrackMode>(key: key, value: trackMode))
                case .timeZone          :
                    guard
                        let rawTimeZone = self.attributes.rawTimeZone,
                        let timeZone = TimeZone(rawValue: rawTimeZone)
                        else {
                            result.append(Setting<TimeZone>.defaultValue(for: key)!)
                            break
                    }
                    result.append(Setting<TimeZone>(key: key, value: timeZone))
                case .language          :
                    guard let language = self.attributes.language else {
                        result.append(Setting<Language>.defaultValue(for: key)!)
                        break
                    }
                    result.append(Setting<Language>(key: key, value: language))
                case .sosContacts       :
                    result.append(Setting<RealmSwift.List<Contact>>(key: key, value: self.attributes.listSosContacts))
                case .favoriteContacts  :
                    result.append(Setting<RealmSwift.List<Contact>>(key: key, value: self.attributes.listFavoriteContacts))
                case .alarms            :
                    result.append(Setting<RealmSwift.List<Alarm>>(key: key, value: self.attributes.listAlarms))
                case .silentMode        :
                    result.append(Setting<RealmSwift.List<SilentInterval>>(key: key, value: self.attributes.listSilentMode))
                case .pedometer         :
                    result.append(Setting<Bool>(key: key, value: self.attributes.pedometer))
                case .watchBattery      :
                    result.append(Setting<Bool>(key: key, value: self.attributes.watchBattery))
                case .notifyOnRemoval   :
                    result.append(Setting<Bool>(key: key, value: self.attributes.notifyOnRemoval))
                case .settingModel      :
                    guard let model = self.model else {
                        result.append(Setting<String>.defaultValue(for: key)!)
                        break
                    }
                    result.append(Setting<String>(key: key, value: model))
                case .settingPhone      :
                    guard let phone = self.phone else {
                        result.append(Setting<String>.defaultValue(for: key)!)
                        break
                    }
                    result.append(Setting<String>(key: key, value: phone))
                case .settingUniqueId   :
                    guard let uniqueId = self.uniqueId else {
                        result.append(Setting<String>.defaultValue(for: key)!)
                        break
                    }
                    result.append(Setting<String>(key: key, value: uniqueId))
                case .settingName       :
                    guard let name = self.name else {
                        result.append(Setting<String>.defaultValue(for: key)!)
                        break
                    }
                    result.append(Setting<String>(key: key, value: name))
                default                 :   break
                }
            }
            return result
        }
        set {
            for setting in newValue {
                switch setting.key {
                case .showOnMap         :   self.attributes.showOnMap = (setting as! Setting<Bool>).value

                case .observers         :
                    self.attributes.listObservers.removeAll()
                    self.attributes.listObservers.append(objectsIn: (setting as! Setting<RealmSwift.List<Observer>>).value)

                case .sosContacts       :
                    self.attributes.listSosContacts.removeAll()
                    self.attributes.listSosContacts.append(objectsIn: (setting as! Setting<RealmSwift.List<Contact>>).value)

                case .favoriteContacts  :
                    self.attributes.listFavoriteContacts.removeAll()
                    self.attributes.listFavoriteContacts.append(objectsIn: (setting as! Setting<RealmSwift.List<Contact>>).value)

                case .alarms            :
                    self.attributes.listAlarms.removeAll()
                    self.attributes.listAlarms.append(objectsIn: (setting as! Setting<RealmSwift.List<Alarm>>).value)

                case .silentMode        :
                    self.attributes.listSilentMode.removeAll()
                    self.attributes.listSilentMode.append(objectsIn: (setting as! Setting<RealmSwift.List<SilentInterval>>).value)

                case .trackingMode      :   self.attributes.trackingMode = (setting as! Setting<TrackMode>).value
                case .timeZone          :   self.attributes.rawTimeZone = (setting as! Setting<TimeZone>).value.rawValue
                case .language          :   self.attributes.language = (setting as! Setting<Language>).value
                case .pedometer         :   self.attributes.pedometer = (setting as! Setting<Bool>).value
                case .watchBattery      :   self.attributes.watchBattery = (setting as! Setting<Bool>).value
                case .notifyOnRemoval   :   self.attributes.notifyOnRemoval = (setting as! Setting<Bool>).value
                case .settingModel      :   self.model = (setting as! Setting<String>).value
                case .settingPhone      :   self.phone = (setting as! Setting<String>).value
                case .settingUniqueId   :   self.uniqueId = (setting as! Setting<String>).value
                case .settingName       :   self.name = (setting as! Setting<String>).value
                default                 :   break
                }
            }
        }
    }
}

class Attributes: Object, Mappable {
    static let empty = Attributes()

    enum Keys: String {
        case showOnMap
        case observers
        case trackingMode
        case timeZone
        case language
        case sosContacts
        case favoriteContacts
        case alarms
        case silentMode
        case pedometer
        case watchBattery
        case notifyOnRemoval
        case settingModel
        case settingPhone
        case settingUniqueId
        case settingName
        case emptySetting

        case modified

        case sat
        case rssi
        case battery
        case steps
        case ip
        case distance
        case totalDistance

        static let settings: [Keys] = [.showOnMap, .observers, .trackingMode, .timeZone, .language, .sosContacts, .favoriteContacts, .alarms, .silentMode, .pedometer, .watchBattery, .notifyOnRemoval, .settingModel, .settingPhone, .settingUniqueId, .settingName]

    let sat = RealmOptional<Int>()
    let rssi = RealmOptional<Int>()
    dynamic var battery: String?
    let steps = RealmOptional<Int>()
    dynamic var ip: String?
    let distance = RealmOptional<Double>()
    let totalDistance = RealmOptional<Double>()

    dynamic var showOnMap = false

    dynamic var rawTrackingMode: String?
    var trackingMode: TrackMode? {
        set {
            guard
                let newValue = newValue
                else {
                    self.rawTrackingMode = nil
                    return
            }
            self.rawTrackingMode = newValue.rawValue
        }
        get {
            guard
                let rawTrackingMode = self.rawTrackingMode,
                let trackMode = TrackMode(rawValue: rawTrackingMode)
                else {
                    return nil
            }
            return trackMode
        }
    }

    dynamic var rawTimeZone: String?

    dynamic var rawLanguage: String?
    var language: Language? {
        set {
            guard let newValue = newValue else {
                self.rawLanguage = nil
                return
            }
            self.rawLanguage = newValue.rawValue
        }
        get {
            guard
                let rawLanguage = self.rawLanguage,
                let language = Language(rawValue: rawLanguage)
                else {
                    return nil
            }
            return language
        }
    }

    let listObservers = RealmSwift.List<Observer>()
    let listSosContacts = RealmSwift.List<Contact>()
    let listFavoriteContacts = RealmSwift.List<Contact>()
    let listAlarms = RealmSwift.List<Alarm>()
    let listSilentMode = RealmSwift.List<SilentInterval>()

    dynamic var pedometer = false
    dynamic var watchBattery = false
    dynamic var notifyOnRemoval = false

    dynamic var modified: String?

    dynamic var alarm: String?

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

    func mapping(map: Map) {
        sat.value           <- map[Keys.sat.rawValue]
        rssi.value          <- map[Keys.rssi.rawValue]
        battery             <- map[Keys.battery.rawValue]
        steps.value         <- map[Keys.steps.rawValue]
        ip                  <- map[Keys.ip.rawValue]
        distance.value      <- map[Keys.distance.rawValue]
        totalDistance.value <- map[Keys.totalDistance.rawValue]
        showOnMap           <- map[Keys.showOnMap.rawValue]

        listObservers           <- map[Keys.observers.rawValue]
        listAlarms              <- map[Keys.alarms.rawValue]
        listSosContacts         <- map[Keys.sosContacts.rawValue]
        listFavoriteContacts    <- map[Keys.favoriteContacts.rawValue]
        listSilentMode          <- map[Keys.silentMode.rawValue]

        trackingMode        <- (map[Keys.trackingMode.rawValue], EnumTransform<TrackMode>())
        rawTimeZone         <- map[Keys.timeZone.rawValue]
        language            <- (map[Keys.language.rawValue], EnumTransform<Language>())
        pedometer           <- map[Keys.pedometer.rawValue]
        watchBattery        <- map[Keys.watchBattery.rawValue]
        notifyOnRemoval     <- map[Keys.notifyOnRemoval.rawValue]
        modified            <- map[Keys.modified.rawValue]
        alarm               <- map["alarm"]
    }
}

Проблема в том, что когда я пытаюсь обновить атрибуты устройства, используя "Интерфейс настроек":

func updateSettings(of device: Device, settings: [SettingType], handler: ((Device) -> Void)? = nil) {
    do {
        try RealmManager.shared.realm.write {
            device.settings = settings
        }
        handler?(device)
    } catch {
        self.fail(with: error)
    }
}

списки в атрибутах обновлений устройства некорректно, я потеряю данные неизмененных списков. Может быть проблема в том, что списки содержат также управляемые объекты Realm (Observer, Alarm, Contact, SilentInterval), но у них нет первичных ключей?

Любая помощь будет оценена.

ОБНОВЛЕНИЕ 1: обнаружена "ошибка", когда я удаляю все объекты из "старого" списка, он также удаляет все объекты из "нового"... Но почему? И как от этого избавиться?

1 ответ

Решение

Так как классы reference typesкогда пишешь let newList = oldList, где oldList это Realm ListВы не копируете содержимое списка, вы просто копируете ссылку. Это означает, что оба newList а также oldList будет указывать на одни и те же объекты в памяти и, следовательно, если вы измените один из них, оба будут изменены.

Скопировать все значения oldList в newList, вы можете создавать новые неуправляемые копии объектов в oldList и хранить их в newList используя

let newList = oldList.map{Setting(value:$0)}

Таким образом, изменения, внесенные вами в один из ваших списков, не будут отражены в другом списке.

Другие вопросы по тегам