I've got a local Realm (using the RealmSwift API version 10.15.1) that has a Player
object that I'm trying to migrate. The Player
currently contains field called preferredPositions
that is a MutableSet<PositionClass>
. The definition of Player
looks like this:
@objc final class Player: Object {
@Persisted(primaryKey: true) var playerId: ObjectId
@Persisted var name: String = ""
@Persisted var preferredPositions: MutableSet<PositionClass> = MutableSet<PositionClass>()
...
}
and PositionClass
looks like this:
class PositionClass: Object {
@Persisted(primaryKey: true) var positionClassId: String = ""
@Persisted var name: String = ""
@Persisted var order: Int = 0
@Persisted var abbreviation: String = ""
...
}
I want to do a migration that will change preferredPositions
from a MutableSet<PositionClass>
to a List<PositionClass>
since now I want preferredPositions
to be ordered.
So the new Player
looks like:
@objc final class Player: Object {
@Persisted(primaryKey: true) var playerId: ObjectId
@Persisted var name: String = ""
@Persisted var preferredPositions: List<PositionClass> = List<PositionClass>()
...
}
However, I can't figure out the magic incantation in the migration configuration to get access to the preferredPositions
data.
In my migration I have:
let schemaVersion: UInt64 = 22
let config = Realm.Configuration(schemaVersion: schemaVersion,
migrationBlock: { migration, oldSchemaVersion in
...
if (oldSchemaVersion < 22) {
migration.enumerateObjects(ofType: Player.className()) { oldObject, newObject in
if let preferredPositionsSet: MutableSet<PositionClass> = oldObject!["preferredPositions"] as? MutableSet<PositionClass> {
let preferredPositionsList: List<PositionClass> = List()
preferredPositionsSet.forEach { (positionClass: PositionClass) in
preferredPositionsList.append(positionClass)
}
newObject!["preferredPositions"] = preferredPositionsList
} else {
NSLog("preferredPositionsSet is nil.")
}
}
}
})
Realm.Configuration.defaultConfiguration = config
But the line
let preferredPositionsSet: MutableSet<PositionClass> = oldObject!["preferredPositions"] as? MutableSet<PositionClass>
always returns nil. I've looked in the debugger and it seems like oldObject!["preferredPositions"]
is a MutableSet<PositionClass>
. For example if I add the code:
let preferredPositionsAny = oldObject!["preferredPositions"]
and then look at preferredPositionsAny
in the debugger it shows:
So, the underlying type is correct, but I don't know how to get at it properly.
Or am I supposed to do the migration in a different way?
Code TL;DR:
let schemaVersion: UInt64 = 22
let config = Realm.Configuration(schemaVersion: schemaVersion,
migrationBlock: { migration, oldSchemaVersion in
...
if (oldSchemaVersion < 22) {
migration.enumerateObjects(ofType: Player.className()) { oldObject, newObject in
let newRealm = newObject!.realm
let preferredPositionsList: List<PositionClass> = List()
let preferredPositionsSet = oldObject!.dynamicMutableSet("preferredPositions")
preferredPositionsSet.forEach { positionClass in
if let newPositionClass = newRealm?.object(ofType: PositionClass.self, forPrimaryKey: positionClass["positionClassId"]) {
preferredPositionsList.append(newPositionClass)
}
}
newObject!["preferredPositions"] = preferredPositionsList
}
}
More Complete Answer:
There were a couple of things I was getting wrong:
MutableSet<PositionClass>
, but a MutableSet<DynamicObject>
. This was why the cast to MutableSet<PositionClass>
was always returning nil
.MutableSet
, so instead of callinglet preferredPositionsSet = oldObject!["preferredPositions"] as! MutableSet<DynamicObject>
from https://stackoverflow.com/a/37588630/1204250 I found
let preferredPositionsSet = oldObject!.dynamicMutableSet("preferredPositions")
Which seems to have the return the same object, but again, a bit more clear
List
with the existing PositionClass
instances (instead of creating new ones), you need to fetch the data from the Realm. However in order to get a parameter to fetch the data. Since the "old" MutableSet
contains DynamicObject
, you need to access it using the DynamicObject method of named fields e.g. positionClass["positionClassId"]
instead of accessing a property on the actual base object type e.g. positionClass.positionClassId
. So that's wherelet newRealm = newObject!.realm
if let newPositionClass = newRealm?.object(ofType: PositionClass.self, forPrimaryKey: positionClass["positionClassId"]) {
...
comes from.