iosswiftrealmrealm-mobile-platform

Error in Declaring Non-Optional Map Property in Realm Swift Object


I have this realm object with a map property:

class UserModel: Object {
       @Persisted var uploadMap = Map<String, UploadingModel>()
}

where UploadingModel is another realm object.

once I ran the app I got this error:

*** Terminating app due to uncaught exception 'RLMException',
reason: 'Map<String, UploadingModel> property 
'uploadMap' must be marked as optional.'

In the official documentation: realm-swift there is an example that initialize the map like the above without marking it as optional. Also, I use other realm types like List<UploadingModel>() and no error is thrown.

How can I avoid mark it as optional and why the error is thrown?


Solution

  • Super good question and the documentation is a little thin in this area.

    Here's why

    TL;DR

    If the value part of a Map is a Realm Object, it can be nil so it must be optional

    Long explanation via an example:

    Start with a Dog Class and then a Class that holds all dog locations as a Map

    class Dog: Object {
        @Persisted var name: String
    }
    
    class AllDogLocations: Object {
       @Persisted var dogLocation = Map<String, Dog?>() //note optional dog here
    }
    

    and then say there two dogs are persisted in Realm, Spot and Scraps and they are added to a dog locations object which is also persisted. Spot is in Ohio and Scraps is in Florida

    let spotDog = Dog()
    spotDog.name = "Spot"
    
    let scrapsDog = Dog()
    scrapsDog.name = "Scraps"
    
    let all = AllDogLocations()
    all.dogLocation["Ohio"] = spotDog
    all.dogLocation["Florida"] = scrapsDog
    
    try! realm.write {
        realm.add(all)
    }
    

    Image then, elsewhere in the app that Spot is deleted from Realm

    let spotDog = realm.objects(Dog.self).where { $0.name == "Spot" }.first!
    try! realm.write {
        realm.delete(spotDog)
    }
    

    What then happens to the AllDogsLocations? Here's what it then looks like

    AllDogLocations {
        dogLocation = Map<string, Dog> <0x600003dd1810> (
        [Florida]: Dog {
                name = Scraps;
            },
        [Ohio]: <null>
        );
    }
    

    Note Spot, which used to be in Ohio, the value is nil because the object no longer exists. It's key in the map however, is left intact.

    However, what would happen if the Value (Dog) was required to not be nil and then that dog was deleted from Realm

    Map<String, Dog>()
    

    Whoops - there's your problem! It must be optional because it COULD be deleted elsewhere in the app!

    Note that in the case of <String, String>, setting the String to nil removes the map entry entirely as that value is required (non-optional)

    There is some debate on the current behavior; what should happen if the Object is set to nil. Let it be nil? Remove it? See Effect of Deleting an Object in a Map?

    And if you are using Realm Sync this Git post is very important Map: Remove Keys When Object Values Become nil #8379