Realm ios sdk concurrency with Swift


I use Realm sdk when developing ios project.

I have problem about Realm sdk concurrency.

So, I recommend how to make code when you use Realm concurrency.


I always met this error when using Realm concurrency:

Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'


There are 4 rules about Realm concurrency:

1. Don't lock to read

Realm eliminates the need to lock for read operations. The values you read will never be corrupted or in a partially-modified state. You can freely read from the same Realm file on any thread without the need for locks or mutexes.

=> It is a little bit difficult for some developers to understand. Based on my experience, it means that you just read Realm database on main thread. If you try to read Realm database on background thread, you would meet "Realm accessed from incorrect thread" error.

Incorrect code example:

func getProducts(forStore store: String) async Results<Product>? {

        do {

            let realm = try await Realm()

            return realm.objects(Product.self).filter("store == %@", store)

        } catch {

            print("Error occurs when getProducts")

            return nil

        }

    }


Correct code example:

func getProducts(forStore store: String) -> Results<Product>? {

        do {

            let realm = try Realm()

            return realm.objects(Product.self).filter("store == %@", store)

        } catch {

            print("Error occurs when getProducts")

            return nil

        }

    }



2. Avoid synchronous writes on the UI thread if you write on a background thread:

You can write to a Realm file from any thread, but there can be only one writer at a time. Consequently, synchronous write transactions block each other. A synchronous write on the UI thread may result in your app appearing unresponsive while it waits for a write on a background thread to complete. 

=> If you write data to Realm database, use Swift concurrency.

Good Example:

func deleteProduct(product: Product) async {

        do {

            let realm = try await Realm()

            try await realm.asyncWrite {

                if let productToDelete = realm.object(ofType: Product.self, forPrimaryKey: product.id) {

                    realm.delete(productToDelete)

                }

            }

        } catch {

            print("Error deleting product: \(error)")

        }

    }


3. Don't pass live objects, collections, or realms to other threads

Live objects, collections, and realm instances are thread-confined: that is, they are only valid on the thread on which they were created. In short, this means you cannot pass live instances to other threads.

=> The result data to read from Realm database must be accessed on UI Thread. Or If you want to use result data on background thread, you need to copy the result data.

Good Example:

func updateFavoriteProducts() async {

        do {

            print(#fileID, #function, #line, "updateFavoriteProducts")


            let realm = try await Realm()

            let favoriteProductsToUpdate = Array(realm.objects(FavoriteProduct.self))  

//copy result data

            try await realm.asyncWrite {

                let allProducts = realm.objects(Product.self)

                var productTitlesMap: [String: Product] = [:]

                for product in allProducts {

                    productTitlesMap[product.title] = product

                }

            }

        } catch {

            print("Error during updateFavoriteProducts: \(error)")

        }

    }


4. Create Realm object when you need

On first try to use Realm, I made Realm object singleton.

However, when you use Realm object, thread where you create realm object must be same as thread where you access realm object.

So, It is recommended that you create Realm object when you need.

Good Example:

func getProducts(forStore store: String) -> Results<Product>? {

        do {

            let realm = try Realm()    //create Realm object when get products

            return realm.objects(Product.self).filter("store == %@", store)

        } catch {

            print("Error occurs when getProducts")

            return nil

        }

    }

    

    func deleteProduct(product: Product) async {

        do {

            let realm = try await Realm()      //create Realm object when delete product

            try await realm.asyncWrite {

                if let productToDelete = realm.object(ofType: Product.self, forPrimaryKey: product.id) {

                    realm.delete(productToDelete)

                }

            }

        } catch {

            print("Error deleting product: \(error)")

        }

    }