Best Practices when using Realm with `AsyncStream` and multiple actors

Hey,

I just found out about Realm while researching database options for a new hobby project I’m working on. From what I’ve read so far, I love everything about it and I am genuinely excited to try it!

I have a few questions regarding some best practices around the way I’m planning on using the framework. I’d massively appreciate any input on this :slight_smile:

The Setup

Let’s say I have a simple DogDTO object that is used within Realm and a Dog struct that the app interacts with:

// Internal DTO object used to store in a Realm database
class DogDTO: Object {
  @Persisted(primaryKey: true) var _id: ObjectId
  @Persisted var name: String
  
  convenience init(name: String) {
    self.init()
    self.name = name
  }
}

// Model that the app uses
public struct Dog: Sendable {
  public var id: String
  public var name: String

  public init(id: String, name: String) {
    self.id = id
    self.name = name
  }
}

As I’m reading through the documentation (especially the parts about Swift Concurrency and actors), I’m wondering how to best implement an interface for the app to interact with the database, and what actors I should use in which case.

Observing Changes

I’m a big fan of using AsyncStreams that the SwiftUI views or view models of the apps can consume to continuously obtain data… So I’m thinking about having a RealmReader class that internally has its own Realm instance and offers a few public methods that create an AsyncThrowingStream to observe changes to database objects. Something like this:

@MainActor public class RealmReader {
  private let realm: Realm
  
  public init() throws {
    self.realm = try Realm()
  }
  
  public func dogListStream() -> AsyncThrowingStream<[Dog], Error> {
    .init { continuation in
      let dogs = realm.objects(DogDTO.self)
      let token = dogs.observe { _ in
        continuation.yield(dogs.map { Dog(id: $0._id.stringValue, name: $0.name) })
      }
      
      continuation.onTermination = { _ in
        token.invalidate()
      }
    }
  }
  
  public func dogStream(dogId dogIdString: String)-> AsyncThrowingStream<Dog, Error> {
    return .init { continuation in
      do {
        let dogId = try ObjectId(string: dogIdString)
        let dog = realm.object(ofType: DogDTO.self, forPrimaryKey: dogId)!
        let token = dog.observe { _ in
          continuation.yield(Dog(id: dog._id.stringValue, name: dog.name))
        }
        
        continuation.onTermination = { _ in
          token.invalidate()
        }
      } catch {
        continuation.finish(throwing: NSError(domain: "dummy", code: 0))
      }
    }
  }
}

For example, the app could then do the following:

do {
  let reader = try RealmReader() // Initialize a new reader (this would happen in a slightly more global place, ideally)
  for try await content in reader.dogListStream() { // Observe changes to the dogs in the database
    print("Update: \(content)")
    // Update view state or whatever
  }
} catch {
  // Handle error
}

I know there are specialized SwiftUI property wrappers that can do this as well, but I usually like to fully separate the UI from the actual data source.

I’m not sure if a MainActor isolation for the read access is appropriate in this case :thinking:. Maybe it should be done on a dedicated actor instead.

Writing Data

However, my gut tells me that writing data to the database should probably be happen in a background thread, to not block the UI. So I’m thinking about having a RealmWriter class isolated to a global background actor, like this:

@globalActor public actor RealmBackgroundActor: GlobalActor {
  public static let shared = RealmBackgroundActor()
}

@RealmBackgroundActor public class RealmWriter {
  private let realm: Realm
  
  public init() async throws {
    realm = try await Realm(actor: RealmBackgroundActor.shared)
  }
 
  public func insertDog(name: String) async throws {
    let dog = DogDTO(name: name)
    try await realm.asyncWrite {
      realm.add(dog)
    }
  }
}

The usage in the app would look like this:

do {
  let writer = await RealmWriter() // Initialize a new writer
  try await writer.insertDog(name: "Foo") // Insert data
} catch {
  // Handle error
}

This does appear to work fine in some fairly basic first tests I did, but I’m not sure if I’m going to run into a wall later on. That can obviously always happen, but I thought I’d ask if there is something obvious I’m missing here.

My Questions

  • A more general question: Do you think there is anything inherently wrong/dangerous with this approach, that I don’t see?
  • Is it be fine to have multiple instances of a RealmReader or RealmWriter in the app? I.e. that each module/feature or whatever uses its own reader and writer to observe and write data. My understanding is that initialising multiple Realm instances is fairly cheap and no real problem, but maybe I’m wrong?
  • Is it a good idea to isolate the RealmReader to the MainActor? Or in other words, would it also be fine to isolate it to its own actor and force asynchronous reading access (which happens anyways, because of the AsyncStream I guess)? If so, should there be two different actors (one for reading, one for writing)? Would it hurt to have two?
  • Something I’ve been wondering: Is there a scenario where try Realm() fails in a recoverable way? No database access kind of feels like a “app can’t continue” scenario, but maybe there’s a situation where another attempt might work?

I’d really appreciate any input on this :slight_smile: Thanks a lot!

Welcome to Realm and to the forums!

The question is pretty “big” and I think answers may be more than what can be done here on the forums as is. It may be better to break the question into smaller chunks so each answer can stay with one topic.

A few questions to help refine the question:

Why is the a DogDTO object as well as a Dog object? Realm objects can be freely used within an app so why not just use one Realm object? Likely no need for two

Is your app doing LARGE background writes? Or is it more one-off type writes like adding a user or updating a single object?

Is this a local app or sync?

The answers to the questions will somewhat depend on the use case.

  • A more general question: Do you think there is anything inherently wrong/dangerous with this approach, that I don’t see?

No, other than it somewhat replicates built in functionality with existing wrappers. If you’re using SwiftUI, definitely take a look at ObservedRealmObject and ObservedResults.

  • Is it be fine to have multiple instances of a RealmReader or RealmWriter in the app? I.e. that each module/feature or whatever uses its own reader and writer to observe and write data. My understanding is that initialising multiple Realm instances is fairly cheap and no real problem, but maybe I’m wrong?

It depends on what ‘multiple instances’ refers to - why does each feature need it’s own? Why not just use one? (or two if you want to write on a background thread). Are you performing gigantic writes from two sections of code simultaneously?

  • Is it a good idea to isolate the RealmReader to the MainActor? Or in other words, would it also be fine to isolate it to its own actor and force asynchronous reading access (which happens anyways, because of the AsyncStream I guess)? If so, should there be two different actors (one for reading, one for writing)?

Are you planning on using Global actors or local?

  • Something I’ve been wondering: Is there a scenario where try Realm() fails in a recoverable way? No database access kind of feels like a “app can’t continue” scenario, but maybe there’s a situation where another attempt might work?

Yes, it could fail hence the “try” but it depends on what is meant by ‘recoverable’. e.g. if the attempt fails, it will throw but the action taken will depend on the nature of the throw.

Hey, thanks for your reply! :slight_smile:

Sorry about the vagueness, I’m mostly still trying to grasp the general fundamentals because I haven’t fully figured out the best way to approach an implementation of Realm into the app.

I see what you mean, there are a lot of parallel threads, sorry about that. I think most of my questions are about highly hypothetical situations that probably will never actually matter for a small to medium sized app like the one I want to build. I’ll try to limit the scope of this thread:


I’m trying to keep the app free of these kinds of things. I want to have app models (like Dog) that are completely agnostic of the underlying data source. This makes it really easy to make changes. For example, before I found Realm, I was experimenting with Firebase and when I created a test implementation with Realm, all I had to do was swap out a data provider class that’s injected into the SwiftUI environment. I didn’t have to touch the app/UI at all.

These SwiftUI features are pretty awesome, and would make things a lot easer in some way :sweat_smile: But it hard-couples the SwiftUI views to Realm objects. If I ever want to have a second data source, I would have to copy and modify the views, for example.

But maybe this thinking is too hypothetical again and I only overcomplicate things for myself.


I don’t expect there to be any really large writes. Maybe occasionally a “batch update” of multiple items, but nothing major. I guess the database interaction would be similar to that of a todo app or reminders app. Lots of small, mostly independent and individual items with some metadata, essentially.

The question about multiple instances is also more a fundamental question about the approach I could take. But the more I think about it the more it seems reasonable to just have a single database interface/instance, that should be absolutely fine.


Local for now, but I’d love to enable sync at some point.


Over the past few hours I’ve come to realize that I really lack the knowledge a about actors and their use case (other than the MainActor). I’ve been working with Concurrency for quite some time, but I’ve never worked with actors, so I’m actually not sure what I want here or rather what would be best. My thinking was just to set it up in a way that writing to the database does not block the main thread, basically. But maybe it’s not really a problem in a small-ish app to just use asyncWrite on a MainActor-isolated Realm instance?

Thanks again for your help!

No addressing the specific question but some general comments.

Firebase and Realm are two very different beasts. I am very familiar with Firebase so let me provide a couple of datapoints

Firebase is an online first database with some support for persistence. Realm is an offline first database with sync’ing. The approach on both the front end and back end is totally different. Here’s the key; Firebase has no objects - any object you craft to ‘hold’ Firebase data is a pure Swift object with no ‘intelligence’ - objects are just objects. Realm on the other hand has Objects - with intelligence.

Part of the choice you make when selecting a database is it’s feature set - one of the features of Realm is that objects are live updating and always reflect the status of the underlying data. Additionally, collections are also live - quite a different feature than Firebase. IMO, creating additional models to make the code ‘agnostic’ defeats (part of) the point of Realm and actually robs you of important and very powerful features.

Creating agnostic code sounds good on paper, but the reality is, you’re going to pick a database because of the features it provides beyond just ‘storing data’.

So, I encourage you to embrace the feature set, if that feature set make sense for your use case. Don’t waste your time coding around it.

I appreciate your thoughts on this :slight_smile: I think I’m going to have a look at the Realm SwiftUI features in detail. They do seem extremely convenient and maybe it’s overkill to artificially separate them, at least to that extreme extend.