I have a case where I need to populate realm with a series of objects upon user request. To populate realm I make a network call then write the objects to realm. When the write is complete, the background thread invokes a callback that switches to the main thread to process the results. I would much prefer to use realm notifications but this is a very specific use case and notifications are not an option.
I can't share my full project but I was able to reproduce this issue with a sample project. Here is my data model.
class Owner: Object {
@objc dynamic var uuid: String = ""
@objc dynamic var name: String = ""
convenience init(uuid: String, name: String) {
self.init()
self.uuid = uuid
self.name = name
}
func fetchDogs() -> Results<Dog>? {
return realm?.objects(Dog.self).filter("ownerID == %@", uuid)
}
override class func primaryKey() -> String? {
return "uuid"
}
}
class Dog: Object {
@objc dynamic var uuid: String = ""
@objc dynamic var name: String = ""
@objc dynamic var ownerID: String = ""
func fetchOwner() -> Owner? {
return realm?.object(ofType: Owner.self, forPrimaryKey: ownerID)
}
convenience init(uuid: String, name: String, ownerID: String) {
self.init()
self.uuid = uuid
self.name = name
self.ownerID = ownerID
}
override class func primaryKey() -> String? {
return "uuid"
}
}
I realize that a List would be appropriate for the Owner -> Dog relationship but that's not an option in this project.
In the sample project there are two buttons that we care about. One to add some Dog objects to realm and associate them with the Owner UUID. The other button deletes all of the Dog objects that belong to the owner.
The basic flow is like this:
- Delete all of the owner's dogs
- Create a new set of dogs on a background thread and add them to realm.
- Invoke a callback
- Switch back to the main thread
- Print all of the owner's dogs
Nine times out of ten the dogs are printed out successfully. However, sometimes realm indicates that there are no dogs associated with the current owner. What could be the reason for this? My view controller logic is below.
EDIT: The last thing I should mention is that this issue is only only reproducible on a physical device. I can't reproduce it on the simulator. That makes me wonder if this is a realm bug.
// MARK: - Props and view controller life cycle
class ViewController: UIViewController {
var owner: Owner?
let ownerUUID = UUID().uuidString
override func viewDidLoad() {
super.viewDidLoad()
owner = makeOwner()
}
}
// MARK: - Action methods
extension ViewController {
@IBAction func onDeletePressed(_ sender: UIButton) {
print("Deleting...")
deleteDogs()
}
@IBAction func onRefreshPressed(_ sender: UIButton) {
print("Creating...")
createDogs {
DispatchQueue.main.async {
self.printDogs()
}
}
}
@IBAction func onPrintPressed(_ sender: UIButton) {
printDogs()
}
}
// MARK: - Helper methods
extension ViewController {
private func makeOwner() -> Owner? {
let realm = try! Realm()
let owner = Owner(uuid: ownerUUID, name: "Bob")
try! realm.write {
realm.add(owner)
}
return owner
}
private func deleteDogs() {
guard let dogs = owner?.fetchDogs() else { return }
let realm = try! Realm()
try! realm.write {
realm.delete(dogs)
}
}
private func createDogs(completion: @escaping () -> Void) {
DispatchQueue(label: "create.dogs.background").async {
autoreleasepool {
let realm = try! Realm()
let dogs = [
Dog(uuid: UUID().uuidString, name: "Fido", ownerID: self.ownerUUID),
Dog(uuid: UUID().uuidString, name: "Billy", ownerID: self.ownerUUID),
Dog(uuid: UUID().uuidString, name: "Sally", ownerID: self.ownerUUID),
]
try! realm.write {
realm.add(dogs)
}
}
completion()
}
}
private func printDogs() {
guard let dogs = owner?.fetchDogs() else { return }
print("Dogs: \(dogs)")
}
}
DispatchQueue.global(qos: .background).async? The second thing is that the way this code is structured, there is no relationship between owners and dogs at all. Those are totally separate, independent objects with no graph between them. Because if that, the question boils down to; I have two sets of objects. If I delete one set of objects, then create another set on a 'background thread' then switch back to the main thread and try to print them and they are not there. - Jayrealmcome from in owner.fetchDogs? You may want to look intoRealm.refresh()and with a quick look, those created dogs appear to be on a totally different thread than the rest of the data. Also this is a good read Using a Realm across threads which may add some clarity - Jayrealmproperty in owner.fetchDogs is inherited by theObjectclass. You are correct, the Dog objects are intentionally created on a background thread. In my actual project we make a network request, store that data in realm on a separate thread, then invoke the callback. The created dogs and the rest of the data are supposed to be on different threads. - RobRealm.refresh()was one solution. If you want to post a solution I'll give you credit. - Rob