Using TCA with Realm

I am rewriting an existing project that utilises Realm using TCA. I have come across something that having a second opinion on would be great.

For example; I have a Favourites section I want to load every type of record that is favourited in Realm into a piece of state. Something like

struct FavouritesState {
    var items: [RealmDatabaseInstance]
}

Inside the reducer I would then fetch these instances from Realm and assign them to the state. My actions are all sent on the main thread and the Effect I use to update the favourites is also received on the main thread. However, when assigning the result of the favourite items in the database to this piece of state I get the error Realm accessed from incorrect thread. which I know is a result of the fact you cannot access realm object from a thread it was not obtained from.

I was able to use the below method to map the results of the Database object to a local struct representation and everything is fine:

func convert(_ object: RealmDatabaseInstance) -> MappedObject {
    return MappedObject(
        text: object.text,
        favourite: object.favourite
    )
}

Therefore - The question; Is this is the suggested approach? Or is there something I am missing. I find it very strange I can't store Realm object types in my state?

Also, when assigning a Realm object that is obtained from the main thread I do not understand why this error is thrown when assigning it to the state?

I'm not familiar with Realm, but I think a few more details would help. Can you show a bit more code of how the Realm object is obtained and then later used?

If the effect that fetches the Realm object is executed on a background thread, then it will not help that you have .receive(on: main) in your code. The object is still created on the background thread, and then delivered on the main thread. So sounds like that will be a problem eventually.

However, instead of performing that convert logic inside your reducer, you could perform that it in the effect. Then you would be able to convert the Realm object on the same thread it was created. Something like this:

case .someAction:
  return environment.fetchInstance()
    .map(convert)
    .receive(on: environment.mainQueue)
    .catchToEffect()
    .map(AppAction.fetchResponse)

See in this situation if fetchInstance happens on a background thread, then convert will be run on the same thread, and then the final result will be delivered to AppAction.fetchResponse on the main thread.

Is that helpful at all?

I have only had a chance to investigate this further very recently. However, I have discovered the cause of the issue. It seems that using the reducer in the debug state was the culprit. Specifically here on line 111 of ReducerDebugging. As the queue for the DebugEnvironment was not the same as where the Realm objects were obtained... BOOM! :boom:

So I was able to use the same queue for the DebugEnvironment as where the objects are obtained and voila... We are back in business :)

Thanks for the response! Definitely helped me realise where I was going on as well as cleaning up my implementation! :+1:

1 Like