In memory realm loses data even though I am calling from same thread
Closed this issue · 4 comments
NOTE: This only happens using InMemoryRealm
configuration something like Realm.Configuration(inMemoryIdentifier: "messagesRealm")
Goals
I am writing a cache protocol which I can implement later using any or multiple cache libraries (regardless of disk or memory), in this case it is Realm
Expected Results
Expectation was when the save function is called realm should successfully save the object in memory and when retrieved it should return it successfully or return all objects.
Actual Results
In memory realm is returning empty every time I insert something and try to fetch it (I am 100% sure it is inserting successfully and I will explain later why)
Steps for others to Reproduce
Reproduce the problem
- Clone the git repository in
Code Sample
- Compile and run on a simulator/device (they both are giving the same results)
- In the
Message
field write anything - Press
Save
- Now press
Refresh
it should populate a list with data but it won't do it
What I have done
- While reproducing the problem navigate to
Realm
->InMemoryRealmCache.swift
and setup a breakpoint at line94
. - Now run the application again and repeat step
3
to4
the application should break after pressingSave
- In the
lldb
writepo realm.objects(MessageRealmEntity.self)
and finally continue the application - Now press
Refresh
and the list will be populated No matter how many times you add and refresh it will work now 100%.
Video to show the problem and how to reproduce
realm-bug_nlFwK7Nu.mov
Code Sample
This is a git repository for a dummy application to reproduce the bug:
https://github.com/Muhand/InMemoryRealm-Bug
Version of Realm and Tooling
Realm framework version: RealmSwift (10.1.4)
Realm Object Server version: ?
Xcode version: Version 12.2 (12B45b)
iOS/OSX version: iOS 14.2
Dependency manager + version: ?
Few important notes
- As can be seen in the project I am initializing
Realm
in every function in my wrapper this way I can avoidRealm accessed from incorrect thread
issue. - However, to avoid realm being initialized on different threads I made sure to set a
DispatchQueue
in which they all are initialized and ran in. - I have tried making my
Realm
object to be static and living somewhere else but this is not scalable and if someone in the team called the functions from a different thread it can crash the app with the errorRealm accessed from incorrect thread
- The wrapper works 100% fine if I am using disk realm instead of memory
- The wrapper is initialized only once so I don't have multiple instances of it
- My first thinking is that Realm is going out of scope but then when I started to setup breakpoints like showing in the video it actually shifted my thinking.
- Creating a strong reference causes error
Realm accessed from incorrect thread
which makes sense
I was able to solve the issue by creating a strong reference to realm (which I never use) and then I reinitialized realm in each function to avoid Realm accessed from incorrect thread
not sure if this is the right way but at least it is a hack for now. I can see why Realm might go out of scope of there is no strong reference but in a use case like mine it doesn't really make sense to create an extra variable
which I never get to use.
here is what I have done
final class RealmInMemoryCache {
private let configuration: Realm.Configuration
private let queue: DispatchQueue
private let strongRealm: Realm <-- Added variable
init(_ configuration: Realm.Configuration) {
self.queue = DispatchQueue(label: "inMemoryRealm", qos: .utility)
self.configuration = configuration
self.strongRealm = try! Realm(configuration: self.configuration) <-- Initialized here
}
}
and with every function in my class I do the following
...
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
completion(.failure(RealmInMemoryCacheError.realmIsNil))
return
}
...
I still don't understand why a breakpoint
"solved" the issue except that xcode debugger might have created a strong reference to Realm
when I write the command po realm.objects(...)
in lldb
that is my only thinking now.
Hi @Muhand !
Thank you for providing detailed information for your issue including a code example.
I have checked out your code example and could reproduce every step you described.
As for your question with the reference please see the documentation:
https://realm.io/docs/swift/latest/#in-memory-realms
Notice: When all in-memory Realm instances with a particular identifier go out of scope with no references,
all data in that Realm is deleted. We recommend holding onto a strong reference to any in-memory Realms
during your app’s lifetime. (This is not necessary for on-disk Realms.)
Data not being held in the in-memory database is actually working as designed. You are basically creating a new realm every time you call
try? Realm(configuration: self.configuration)
Your approach of adding a strong reference is therefore the correct solution, you would want to pass the DispatchQueue
you created for working with realm into its initialiser though.
Be aware though that doing this in the initialiser of RealmInMemoryCache
like this
self.strongRealm = try! Realm(configuration: self.configuration, queue: self.queue)
will lead to a Realm opened from incorrect dispatch queue.
error if done on another thread.
My suggested solution for your example would be:
private lazy var realm: Realm = {
return try! Realm(configuration: self.configuration, queue: self.queue)
}()
No you can remove all occurrences of
guard let realm = try? Realm(configuration: self.configuration) else {
completion(.failure(RealmInMemoryCacheError.realmIsNil))
return
}
from your code and just access the member self.realm
instead.
Does this solve your problem sufficiently?
Kind regards,
Dominic
Dominic,
Thanks for your suggestion, I will definitely implement it. That would solve my problem indeed.
Thanks again,
Muhand
Hi @Muhand, glad to hear your problem is solved!