0

Consider the following example:

class Item: Object {
    @objc dynamic var id: UUID = UUID()
    
    @objc dynamic var value: Int = 0
    
    override class func primaryKey() -> String? {
        return "id"
    }
}


let realm = try! Realm()

try! realm.write {
    let query1 = realm.objects(Item.self)
    precondition(query1.isEmpty)
    let newItem = Item()
    realm.add(newItem)
    precondition(query1.contains(newItem))

    let query2 = realm.objects(Item.self).filter("value == 12")
    precondition(query2.isEmpty)
    newItem.value = 12
    precondition(query2.count == 1 && query2[0] == newItem)
}

When running this program, I can observe that Realm does in fact live-update queries, and all of the preconditions pass.

My question is the following: is this behaviour guaranteed? I.e., does Realm guarantee that accessing a query will always return the current in-memory version of objects of that query's predicate (as long as you're on the same thread that created the query), even for changes that have not yet been committed to disk (by ending the write transaction)?

Note: I am asking only about the behaviour when manually accessing a query object (e.g., by checking its count or whether it contains some object, as done in the example above). I am NOT asking about the behaviour of observers, which obviously won't get called until the write transaction is actually committed.

lukas
  • 2,300
  • 6
  • 28
  • 41
  • Interesting question. Can you explain the use case? e.g. why query for an object(s) that may or may not exist a moment later. The write is a transaction and either it all succeeds or all fails - if it fails, the object does not exist because it was not persisted and also goes out of scope - and in your example, the query & results do not exist either since they go out of scope as well. – Jay Mar 04 '23 at 14:44
  • @Jay basically exactly that. i want to run a massive write operation (essentially a database integrity check that restores some invariants if necessary) and i want it to either all fail or all succeed. currently, this is split up into several writes (because i don't know whether i can rely on queries in writes being up-to-date) and ideally i'd like to have it all as one single write. no queries/etc would outlive the transaction. – lukas Mar 04 '23 at 22:09

1 Answers1

0

Background for completeness:

Realm writes are called transactional writes. Meaning once the transaction starts, you can freely read, write and modify data within the transaction and the transaction is a single indivisible operation.

The reads and writes within a transaction either all pass or all fail - if an exception is thrown within the transaction, the entire transaction is canceled and data is "rolled back" to pre-transaction

Answer:

All reads and writes within a transaction are synchronized to the transaction - if a query is generated within the write and data is written that conforms to that query, it will exist within the query results; that means reads reflect the current status of the data as it exists within the transaction.

Note that large transactions opened on the UI thread may appear to block the UI so best practice is to open them on a background thread. Writes are blocking so only do them on one thread at a time.

For example, suppose we have a PersonClass Object

realm.writeAsync {
    print("-- before write --")
    
    //generate a query
    let results = realm.objects(PersonClass.self)

    //output the current Persons
    for person in results {
        print(person.name)
    }
    
    print("--writing--")
    
    //add two more people
    let p = PersonClass(name: "Jay")
    realm.add(p)
    
    let j = PersonClass(name: "Cindy")
    realm.add(j)
    
    print("--done writing--")

    //output the results again; it will contain Jay and Cindy Person
    for person in results {
        print(person.name)
    }
}
Jay
  • 34,438
  • 18
  • 52
  • 81