Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data written to a background Realm is not available immediately to main Realm

Tags:

swift

realm

I have a setup like the following:

// Queues
private static let mainQueue = dispatch_get_main_queue()
private static let writeQueue = dispatch_queue_create("com.tablelist.Tablelist.queue.realm.write", DISPATCH_QUEUE_SERIAL)

// Realms
private static let defaultRealm: Realm = try! Realm()

private static func getDefaultRealm(block: (Realm) -> ()) {
    Dispatch.async(mainQueue) {
        block(defaultRealm)
    }
}

private static func getWriteRealm(block: (Realm) -> ()) {
    Dispatch.async(writeQueue) {
        block(try! Realm())
    }
}

Originally I had a writeRealm but since GCD makes no guarantee about which thread a block in a queue is run, I was forced to create a new Realm each time in the write func.

I then have a public func:

/**
    Asynchronously write data to the realm
*/
public static func write(block: (Realm) -> ()) -> Promise<Realm> {
    let promise = Promise<Realm>()

    getWriteRealm { writeRealm in
        do {
            try writeRealm.write {
                block(writeRealm)
            }
            getDefaultRealm { realm in
                promise.resolve(realm)
            }
        }
        catch {
            Dispatch.main {
                promise.resolve(error)
            }
        }
    }

    return promise
}

This allows the caller to pass in a block where it can do any importing, and then fetch any imports on the main thread when the promise resolves. The problem is, sometimes the imported data is available to the Realm on the main thread and sometimes it isn't. Is there a better approach here?

EDIT: Just to clarify, if I change the write func to grab the default realm in both cases, all of my tests pass.

SOLUTION:

private static func getDefaultRealm(block: (Realm) -> ()) {
    Dispatch.async(mainQueue) {
        defaultRealm.refresh() // refresh the realm to bring to most recent state
        block(defaultRealm)
    }
}

private static func getWriteRealm(block: (Realm) -> ()) {
    Dispatch.async(writeQueue) {
        let realm = try! Realm()
        realm.refresh() // refresh the realm to bring to most recent state
        block(realm)
    }
}

SOLUTION 2: (After simplifying further)

private static func getDefaultRealm(block: (Realm) -> ()) {
    let queue = dispatch_get_main_queue()
    getRealm(queue, block: block)
}

private static func getWriteRealm(block: (Realm) -> ()) {
    let queue = dispatch_queue_create("com.tablelist.Tablelist.queue.realm.write", nil)
    getRealm(queue, block: block)
}

private static func getRealm(queue: dispatch_queue_t, block: (Realm) -> ()) {
    Dispatch.async(queue) {
        let realm = try! Realm()
        realm.refresh()
        block(realm)
    }
}
like image 323
Andrew Avatar asked Sep 11 '15 14:09

Andrew


Video Answer


1 Answers

tl;dr; call Realm.refresh() to advance a transaction to the latest state.

Realm's transactions are isolated to provide self-consistency. This allows performing transactions on any thread, at any time, without ever requiring you explicitly lock or use other types of resource coordination.

Both read and write transactions in Realm are based off the most recent successful write commit when first initialized, and remain on that version until refreshed. Realms are automatically refreshed at the start of every runloop iteration, unless Realm's autorefresh property is set to false. If a thread has no runloop (which is generally the case in a background thread), then Realm.refresh() must be called manually in order to advance the transaction to the most recent state.

Realms are also refreshed when write transactions are committed (Realm.commitWrite()).

like image 117
jpsim Avatar answered Nov 03 '22 02:11

jpsim