Realm Migration Error: 'RLMException': 'Property 'RecipeStep.recipeId' does not exist' - ios

I'm running into issues during a Realm migration. The error messages says that the property doesn't exist, but this is what my new object looks like:
class RecipeStep: Object {
#objc dynamic var recipeId: Int = 0
let stepNumber = RealmOptional<Int>()
#objc dynamic var stepText: String? = nil
}
And here's what the old object schema looks like:
class RecipeStep: Object {
let recipeId = RealmOptional<Int>()
let stepNumber = RealmOptional<Int>()
#objc dynamic var stepText: String? = nil
}
As you can see, the single change is the type of the recipeId: converting RealmOptional to an Int. And here's the migration block I'm using to do it:
migration.enumerateObjects(ofType: RecipeStep.className()) { oldObject, newObject in
if let recipeStepRecipeId = oldObject?["recipeId"] as? Int {
newObject?["recipeId"] = recipeStepRecipeId
}
}
What am I doing wrong?

This turns out to have been a bug in an older version of Realm (v5.4.5). I updated to version 10.0.0 and this issue was resolved. The migration successfully completes. I changed nothing in my code.
I'm on Xcode 12, building for iOS 13.
Found discussion on a similar sounding bug on this SO thread.

Related

Is Realm smart when updating values or check for new values should be perfomed manually?

I wonder if I should create my own additional layer when updating Realm objects to avoid redundant database writing operations or is it done automatically on a lower level?
Let's take an example:
class SomeEntity: Object {
#Persisted(primaryKey: true) var id = 0
#Persisted var aaa: String?
#Persisted var bbb: Float?
#Persisted var ccc: Int?
}
when doing some batch update:
newDownloadedData.forEach { entry in
guard let id = entry["id"].int else {
return
}
try? localRealm.write {
let entity = existingLocalEntities.first { $0.id == id } ?? SomeEntity(id: id)
localRealm.add(entity, update: .modified) //this makes an 'upsertion' which is automatically an update or insert
if entity.aaa != entry["aaa"].string {
entity.aaa = movieInfo["aaa"].string
}
if entity.bbb != entry["bbb"].float {
entity.bbb = movieInfo["bbb"].float
}
if entity.ccc != entry["ccc"].int {
entity.ccc = movieInfo["ccc"].int
}
}
}
I wonder if these checks necessary or can I just go with:
entity.aaa = movieInfo["aaa"].string
entity.bbb = movieInfo["bbb"].float
entity.ccc = movieInfo["ccc"].int
and not worry that values will be updated and written even if downloaded values are the same as existing local ones?
Your observers will be notified if you update a property on a realm object with the same value. Realm does not care if you use a different value or not.
I'm not sure what your use case is, but it may be a pain in the butt to check every value manually.
You can do something like this though:
protocol UniqueUpdating { }
extension UniqueUpdating where Self: AnyObject {
#discardableResult
func update<Value: Equatable>(
_ keyPath: ReferenceWritableKeyPath<Self, Value>,
to value: Value
) -> Bool {
guard self[keyPath: keyPath] != value else { return false }
self[keyPath: keyPath] = value
return true
}
}
extension Object: UniqueUpdating {}
class Person: Object {
#Persisted(primaryKey: true) var id: Int = 0
#Persisted var name: String = ""
}
Usage would be like this:
let realm = try! Realm()
try! realm.write {
person.update(\.name, to: "BOB")
}
EDIT: things described below do not work like expected.
eg. if name = "Tom" and later the same value is assigned ( self.name = "Tom") it will be treated as modification.
It turns out that YES, Realm can be smart about it!
the clue sits in update parameter in .add function.
using .modified will result in smart data write.
Excerpt from documentation:
/**
What to do when an object being added to or created in a Realm has a primary key that already exists.
*/
#frozen public enum UpdatePolicy: Int {
/**
Throw an exception. This is the default when no policy is specified for `add()` or `create()`.
This behavior is the same as passing `update: false` to `add()` or `create()`.
*/
case error = 1
/**
Overwrite only properties in the existing object which are different from the new values. This results
in change notifications reporting only the properties which changed, and influences the sync merge logic.
If few or no of the properties are changing this will be faster than .all and reduce how much data has
to be written to the Realm file. If all of the properties are changing, it may be slower than .all (but
will never result in *more* data being written).
*/
case modified = 3
/**
Overwrite all properties in the existing object with the new values, even if they have not changed. This
results in change notifications reporting all properties as changed, and influences the sync merge logic.
This behavior is the same as passing `update: true` to `add()` or `create()`.
*/
case all = 2
}

Why do my linking objects not have a Realm?

In my iOS app, I get the following exception:
'Linking objects notifications are only supported on managed objects.'
when I try to add an observer block:
y.xxx.observe { ... }
to a property that is defined as such:
class Y: Object {
...
let xxx = LinkingObjects(fromType: X.self, property: "y")
...
}
I believe this means that y.xxx does not have a Realm, and indeed I can see in the debugger that y.xxx.realm is nil. However, y.realm is NOT nil.
How can the linking objects not have a Realm if the object I am linking to does have one?
For completeness, this is how Class X is defined:
class X: Object {
...
#Persisted var y: Y?
...
}
Realm version 10.11.0, RealmDatabase version 11.1.1.
Context: I am in the last phase of migrating an app that was originally written in ObjC to be purely in Swift. This means switching to the Swift version of Realm. I have not encountered this problem in the previous version of the app that is largely the same code base except that it uses a very old version of the Realm framework and the Realm objects are defined in ObjC.
You can add an observer to a linking objects property as long as the objects are managed. Let me set this up starting with the PersonClass who has a List property of DogClass objects
class PersonClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = "Jay"
let dogList = List<DogClass>()
override static func primaryKey() -> String? {
return "_id"
}
}
and then our DogClass has an inverse relationship to the PersonClass objects
class DogClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = ""
let linkingOwners = LinkingObjects(fromType: PersonClass.self, property: "dogList")
override static func primaryKey() -> String? {
return "_id"
}
}
Then suppose we want to observe Spot's linkingOwners - both Jay and Cindy had Spot added to their dogList previously
let spot = realm.objects(DogClass.self).filter("name == 'Spot'").first!
self.peopleToken = spot.linkingOwners.observe { changes in
switch changes {
case .initial(let dogs):
print(" spots owners have been loaded")
case .update(_, let deletions, let insertions, let modifications ):
print(" something changed in spots owners")
case .error(let error):
print(error.localizedDescription)
}
}
Running this section of code outputs this to console and adds the observer the linkingObjects
spots owners have been loaded
Then, lets make a change to one of the owners properties
let jay = realm.objects(PersonClass.self).filter("name == 'Jay'").first!
try! realm.write {
jay.name = "Jay, Spots owner"
}
that last piece of code will output this to the console
something changed in spots owners
The above code creates a PersonClass object and a DogClass object object with a inverse relationship. The code then adds an observer the linkingObjects (PersonClass) and fires when one of them changes.
Turns out the linking objects now have to be declared like so:
class Y: Object {
...
#Persisted(originProperty: "y") var xxx: LinkingObjects<X>
...
}
I am not sure if the declaration style I used in my question is still supposed to be valid and if this is a bug, but using the new style gets rid of the exception.

Instance of Realm Object subclass is null when init

I'm new to iOS development and swift, and I'm using Realm for my swift project.
First, I create a subclass of Realm Object:
enum EnumA: Int {
case ValueA
case ValueB
}
class ClassA: Object {
var propA: EnumA = EnumA.ValueA
var propB: Double = 0.0
}
Then I have another class:
class ClassB: Object {
var id = 0
var name: String = ""
let aLotOfA = List<ClassA>()
override static func primaryKey() -> String? {
return "id"
}
}
Then I create instance of ClassB somewhere:
class ClassC: NSObject {
static let cManager = ClassC()
func defaultB() -> ClassB {
let instanceA = ClassA()
let instanceB = ClassB()
instanceB.name = "String"
instanceB.aLotOfA.append(instanceA)
return instanceB
}
}
And I have this class:
class ClassD: Object {
let aB: ClassB = ClassC.cManager.defaultB()
}
When I call defaultB(), the first line (let instanceA = ClassA()) makes instanceA null. I keep receiving message in the console like this:
"Object type '(null)' does not match RLMArray type 'ClassA'."
or
"The `ClassD.aB` property must be marked as being optional."
I don't know what's wrong here. Please someone help me, thanks very much.
And my environment:
Mac OS X 10.11.1 + Xcode 7.1
Realm is latest (Just downloaded from realm.io)
Base SDK: iOS 9.1
Deployment Target: iOS 9
As pointed out by zuziaka, you will need to declare all your persisted properties with dynamic var. This doesn't apply for List and RealmOptional properties.
Furthermore Realm doesn't support enums. You will need to declare your property ClassA.propA as Int and use the rawValue to initialize its default value:
class ClassA {
var propA: Int = EnumA.ValueA.rawValue
…
}
To-one relationships must be always marked as optional. That's here the case for the property ClassD.aB.

Realm primary key migration

I want to migrate my realm schema to a new version. Therefor the removal of my primary key is needed.
Old schema:
class StudyState : Object
{
dynamic var name = ""
dynamic var x = ""
dynamic var y = ""
override static func primaryKey() -> String? {
return "name"
}
}
New schema:
class StudyState : Object
{
dynamic var name = ""
dynamic var x = ""
dynamic var y = ""
}
Without migration, realm will fail with
'RLMException', reason: 'Migration is required for object type 'StudyState' due to the following errors:
- Property 'name' is no longer a primary key.'
I tried this migration block, which failed too:
migration.enumerate(StudyState.className()) { oldObject, newObject in
newObject?["deleted"] = false
newObject?["primaryKeyProperty"] = ""
}
'RLMException', reason: 'Invalid property name'
Is there a way to remove the primary key when migrating realm to a new schema version?
You do not need to do anything in migration block if you only remove the primary key annotation.
But there is a need to increase the schema version because schema definitions changed.
Like below:
// You have to migrate Realm BEFORE open Realm if you changed schema definitions
setSchemaVersion(1, Realm.defaultPath) { (migration, oldSchemaVersion) -> Void in
if oldSchemaVersion < 1 {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
}
let realm = Realm()
...

Type casting an RLMObject throws away data

I have code that saves an RLMObject subclass to the realm database. This code works and I have used the realm browser to verify that it is saved as expected.
I then want to query the realm database for this object that I saved, and I want to cast it to the RLMObject subclass that is was before I saved it.
Here is the code:
let queryResults = RealmSubclass.allObjects()
for result in queryResults {
if result is RealmSubclass {
let temp = result as RealmSubclass
println(temp.name)
println(temp.dateOfBirth)
println(temp.gender)
}
}
When I check the values in the debug console, using print object, I see values that I expect. However, when I do a type cast to RealmSubclass the resulting object has no correct values, only nil values.
Why could this be? I have read the documentation, to no avail.
EDIT:
Here is the RLMObject subclass:
public class RealmSubclass: RLMObject {
public dynamic var id: String = NSUUID().UUIDString
public dynamic var name: String = ""
public dynamic var dateOfBirth: NSDate = NSDate()
public dynamic var gender: NSString = Consts.Gender.Male
override public class func primaryKey() -> String {
return "id"
}
}
Ok, it seems that the values were actually being returned. What happened is that Swift debugging is not up to standard yet. The debug area was showing incorrect information.

Resources