Inmemory Realm Threading in Swift

InMemory Realm Threading in Swift

For starters, I'd recommend absolutely checking to make sure your main thread Realm reference isn't being deallocated. I personally would use a singleton object to store the main Realm reference on the main thread. That would be the simplest reason why your Realm is staying empty.

Beyond that, when you do a write to a Realm in the background, those changes are only reflected in the main thread on the next iteration of the run loop. If you need the changes to be reflected before that, you can call realm.refresh() to explicitly request it to pull the latest changes.

In memory realm loses data even though I am calling from same thread

After reading the documentation of Realm again and again and keep thinking of what @Jay have said in the comments, I paid more attention to this quote from the documentation

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.)

Key sentence in the above quote is

When all in-memory Realm instances with a particular identifier go out of scope with no references, all data in that Realm is deleted.

In other words my Realm object is going out of scope everytime I try to save or fetch or do any other function

for example:

self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
completion(.failure(RealmInMemoryCacheError.realmIsNil))
return
}

guard let object = object as? RealmSwift.Object else {
completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
return
}


do {
try realm.write { () -> () in
realm.add(object, update: .all)
completion(.success(()))
return
}
} catch {
completion(.failure(RealmInMemoryCacheError.realmError))
return
}
}

In the above code Realm goes out of scope as soon as that queue is done with it. Then Realm will look if there is any other variable in memory with the same identifier if so then it does nothing otherwise it will delete the current realm to optimize.

So the solution to the problem is basically to create a strong reference to realm with this identifier and then in each function re-create realm with the same identifier as well to avoid Realm accessed from incorrect thread This will however render in an extra variable that is not used but I think that is okay for now, at least it is my solution until something official comes from Realm. Keep in mind the process of re-initializing should not be an overhead since Realm takes care of that optimization.

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 in other functions I do something like the following

...
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
completion(.failure(RealmInMemoryCacheError.realmIsNil))
return
}
...

What threw me off thinking my Realm was going out of scope is when I setup breakpoints Realm started behaving perfectly fine. Although I still don't know 100% for sure why but my thinking is that xcode debugger might have created a strong reference to Realm when I write the command po realm.objects(...) in lldb.

I will accept this answer for now, unless someone has a better solution.

Reaching Realm Results object from different thread gives crash

Realm objects can only be used from the same thread that they were fetched.

So you have 2 main options, if you are going to use any of your data to populate UI, you'll have to fetch the objects in the main thread.

The other option is to create a new Realm instance and fetch the results again in the main thread, creating a Realm is supposed to be really lightweight.

You can also pass objects between threads but that is more complicated.

Realm instances with background process loses data

It sounds like it should work fine. You'll have to try it, and that's probably quicker than waiting for a definitive answer on here.

I'd write a singleton class, that as part of the constructor defines the Realm configuration and stores it, and also opens and stores an instance of that realm. Then I'd add an accessor to that class for your threads to use; it would return a new instance of the realm using the same configuration. When those go out of scope (and you should allow them to go out of scope) your realm will persist due to the singleton instance maintaining its reference.

I may be reading your first referenced question wrongly, but that isn't dealing with in-memory realms and therefore doesn't demand that you hold onto a reference somewhere.

Is it safe to perform Realm local migration in background thread?

Creating a new RLMRealmConfiguration and calling setDefaultConfiguration are trivial operations. Neither of them trigger migrations.

Migrations don't run until you open a realm.

There are a couple of ways to cause the migration to run in a background thread. I'll show you the easiest way in Swift. You'll have to translate this code to Objective-C:

enum Migrator {

static func migrate(completion: @escaping () -> Void) {

let queue = DispatchQueue(label: "migrator.awesome")

let configuration = Realm.Configuration(
schemaVersion: 13,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
// ...
}
if oldSchemaVersion < 2 {
// ...
}
if oldSchemaVersion < 3 {
// ...
}
// ...
}
)

Realm.asyncOpen(configuration: configuration, callbackQueue: queue) { result in

Realm.Configuration.defaultConfiguration = configuration

switch result {
case .failure(let error):
print("error", error)
case .success(let realm):
print("realm", realm)
break
}
completion()
}
}
}

asyncOpen will open a realm and run the migration on the specified queue. In your case, you want to pass in a background queue. Once the migration runs asyncOpen will invoke the specified callback on the same queue you specified.

Once the migration is complete you can switch back to the main thread and do whatever you need to do. And yes, the data that was migrated in the background thread will be available to the main thread.

enum RealmInitializer {
static func initializeRealm() {
Migrator.migrate {
DispatchQueue.main.async {
readAndWriteRealmData()
}
}
}
}

Realm Swift: passing unpersisted objects between threads

You are free to pass unpersisted objects between threads as you wish -- only persisted objects cannot yet be handed over between different threads.

RLMException when save an object in a background thread

Realm Object instances can't be moved between threads once they've been saved to Realm. Since school would be an object that was instantiated on the main thread, you're creating a conflict by attaching it to an un-persisted object and moving the lot to a background thread.

To fix this, you'll need to make a background version of the school object using Realm's thread reference feature.

Also, unless you have a specific reason for creating Person on the main thread, I'd also recommend moving its creation to the background thread too.

let schoolRef = ThreadSafeReference(to: school)

DispatchQueue.global().async {
do {
let realm = try Realm!

let person = new Person()
person.name = "John"
person.school = realm.resolve(schoolRef)!

try realm.write {
realm.add(person, update: true)
}

DispatchQueue.main.async {
// Success!
}
} catch let error as NSError {
DispatchQueue.main.async {
// Error!
}
}
}

Thread-safe way of instantiating optional Realm objects

Your problem exists due to the fact that you are using ignored properties, which are not persisted by Realm. According to this GitHub issue about ignored properties, what you experience is the expected behaviour.

From the linked issue:

"Ignored properties are only guaranteed to be consistent across
individual Swift instances, but retrieving an object from a collection
creates a new Swift instance each time, thus the behavior that you
see."

This means that the self.collection.players[i] inside locationManager(didUpdateLocations:) is actually a new Swift instance of the same Realm object that you created in loadPlayers(), which is not an issue with normal Realm properties, since they are fetched from the Realm database, but is causes an issue with ignored properties, since they are linked to a specific Swift instance and only exist in memory. Hence, when you retrieve a specific object from a collection (Player instance from `List'), all ignored properties are lost.

To sum it up, you won't be able to use ignored Realm properties in collections.



Related Topics



Leave a reply



Submit