Pattern for background thread re-use?


#1

Hello, new to Realm (and Swift) here and have a question about keeping my code DRY when doing background threads in realm.

I am using Eureka forms, and the definition I have is like this:

form
+++ Section()

<<< TextRow("name") {
    $0.title = "Name"
    $0.placeholder = "My home"
    $0.add(rule: RuleRequired())
    if homeProfile.name != "" {
        $0.value = homeProfile.name
    }
    }.onChange { row in
        if let value = row.value {
            DispatchQueue.global(qos: .background).async {
                let realm = try! Realm()
                if let homeProfile = realm.object(ofType: HomeProfile.self, forPrimaryKey: 1) {
                    try! realm.write {
                        homeProfile.name = value
                    }
                }
            }
        }
    }

<<< ZipCodeRow("zip") {
    $0.title = "Zipcode"
    $0.placeholder = "10001"
    $0.add(rule: RuleRequired())
    if homeProfile.zip != "" {
        $0.value = homeProfile.zip
    }
    }.onChange { row in
        if let value = row.value {
            DispatchQueue.global(qos: .background).async {
                let realm = try! Realm()
                if let homeProfile = realm.object(ofType: HomeProfile.self, forPrimaryKey: 1) {
                    try! realm.write {
                        homeProfile.zip = value
                    }
                }
            }
        }
    }

As you can see, I am duplicating a large amount of code just to update the name vs the zip. Is there a way to pull out all of the DispatchQueue.global... logic into a separate function? I tried it with generics but could not figure out how to specify a specific field like “.zip” or “.name” as a parameter. Is this possible with realm while remaining thread safe?

Something like:

func bgUpdate(fieldName: String, value: Any) {
    DispatchQueue.global(qos: .background).async {
        let realm = try! Realm()
        if let homeProfile = realm.object(ofType: HomeProfile.self, forPrimaryKey: 1) {
            try! realm.write {
                homeProfile[fieldName] = value // this doesn't actually work, just example of what I want
            }
        }
    }
}

...later 
bgUpdate('name', 'My home')
bgUpdate('zip', 10001)

?


#2

I was able to solve this using a callback func like this:

    func updateFieldAsync(callback: @escaping (HomeProfile) -> Void) {
        self.dispatchQueue.async {
            let realm = try! Realm()
            if let homeProfile = realm.object(ofType: HomeProfile.self, forPrimaryKey: 1) {
                try! realm.write {
                    callback(homeProfile)
                }
            }
        }
    }

then use like this:

.onChange {
    [weak self]
    row in
    if let value = row.value {
        self?.updateFieldAsync() {
            hp in
            hp.name = value
        }
    }
}

#3

I was able to make more generic like so:

func updateFieldAsync<T>(callback: @escaping (T) -> Void) {
    DispatchQueue.global(qos: .background).async {
        let realm = try! Realm()
        if let objectToUpdate = realm.object(ofType: T.self as! Object.Type, forPrimaryKey: 1) {
            try! realm.write {
                callback(objectToUpdate as! T)
            }
        }
    }
}

and call it like:

if let value = row.value {
    self?.updateFieldAsync() {
        (hp: HomeProfile) in
        hp.name = value
    }
}

I have primary key hardcoded because I want only one of these objects in my app, but trivial to add that as a param instead… hope it helps :slight_smile:


#5

I’m new to realm and right now I’m trying to learn how to perform a lot of realm operations on a background thread and the refresh on the main thread.

I was reading up on on notifications etc but I’d really like to just be able to invoke a callback function.
(Mainly because I’m working with a very inflexible codebase).

In your example, if callback was dispatched on the main thread and then ‘objectsToUpdate’ then used in a viewController or something like that would I get ’ realm object wrong thread’ error?

Something like this is what I mean


func viewWillAppear() {

    RealmManager.updateFieldAsync(callback:{[objects:MyObjects] in 
    
    DispatchQueue.main.async {
        self.showListOfObjects(objects)
    }
    })


    func showListOfObjectsOnScreen(objects:[MyObjects]) {

        // do stuff on main thread with objects
    }
}

(first post here so apologies if I’m breaking etiquette )


#6

You can’t pass objects between threads. If you want to pass a reference i.e. the primary key for the objects and then fetch them from the main thread.

I have an app that needs to display a list of items and a bunch of associated calculated values - complex queries to count things in the database. So I use a background thread to populate an array of structs that contain the object ID, the object type and enough information to display in the list. This background thread calls a callback on the ViewController and passes a batch of items which the view controller adds to the list and inserts into the OutlineView or TableView. If the user selects one of the items in the list then, depending on the action the user requests I will fetch the object from the main thread Realm. I use a singleton object to manage opening realms and if the request comes from the main thread then I will pass a reference to the already open main thread realm. I think Realm might do this under the covers anyway.

You could also just do a query using the object ID’s to get the list of realm objects and use those to directly populate your ViewController - and register for notifications etc. to keep the UI updated with any changes made on background threads.