Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observing changes to Realm result set

Tags:

ios

swift

realm

I'm working on an iOS app that uses a Realm database. In my AppDelegate, I define a Realm result set like this:

var results: Results<RealmWidget>!
var notificationToken: NotificationToken? = nil

do {
    let realm = try Realm()
    if results == nil {
        results = realm.objects(RealmWidget.self)
    }
} catch {
    print(error.localizedDescription)
}

I observe this result set for changes like this:

if notificationToken == nil {
    notificationToken = results.observe { (changes: RealmCollectionChange) in
        switch changes {
        case .update(_, _, let insertions, _):
            if insertions.count > 0 {
                // show badge on tab bar
                // play sound to get the user's attention
            }
        default:
            break
        }
    }
}

Anytime a new RealmWidget object is inserted in the database (which happens as a result of receiving data from a server), I want to show a badge on the UITabBar in my UI. This code appears to be working properly.

In another part of my app, I have a view controller with a UITableView that is used to display all of the RealmWidget objects (sorted by a timestamp). Inside of that view controller, I also have a result set that I observe for changes so I know when to reload the tableview. That code looks like this:

var results: Results<RealmWidget>!
var notificationToken: NotificationToken? = nil

class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  override func viewDidLoad() {
      super.viewDidLoad()

      results = RealmHelper.getRealmWidgets() // helper method that  queries Realm for all of the Widget objects and sorts them by timestamp
      notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
          DispatchQueue.main.async {
              self!.tableView.reloadData()
          }
      }
  }
}

This code also appeared to be working properly until I added code to play a sound into the observation block in my AppDelegate. Then I realized that anytime I delete a row from the UITableView in this view controller, I was also hearing the sound play. This was unexpected because I should only be adding the badge to the UITabBar and playing the sound when there is an insertion, not a deletion.

I added some debug code into the observation block that's defined in my AppDelegate so it looks like this:

if notificationToken == nil {
    notificationToken = results.observe { (changes: RealmCollectionChange) in
        switch changes {
        case .update(_, let deletions, let insertions, let modifications):
            print("widget deletion count: \(deletions.count)")
            print("widget insertion count: \(insertions.count)")
            print("widget modification count: \(modifications.count)")
            if insertions.count > 0 {
                // show badge on tab bar
                // play sound to get the user's attention
            }
        default:
            break
        }
    }
}

When I run the app, switch to the view controller with the tableview and then delete one of the rows (which causes the corresponding RealmWidget object to be deleted from the database), I see the following output printed in the debug console:

widget deletion count: 2
widget insertion count: 1
widget modification count: 0

A couple of things about this are unclear to me:

  • why is the deletion count 2 when I only deleted one RealmWidget object?
  • why is the insertion count 1 when I didn't add/insert any RealmWidget objects?

If anyone can explain what's happening here I would certainly appreciate the help!

-- EDIT --

I added the same debug code into my observation block that is defined inside the view controller and noticed something else strange. When this "problem" occurs, I see this output in the debug console:

widget deletion count (AppDelegate): 2
widget insertion count (AppDelegate): 1
widget modification count (AppDelegate): 0

widget deletion count (View controller): 1
widget insertion count (View controller): 0
widget modification count (View controller): 0

So the observation block in my view controller shows 1 deletion but the observation block in my AppDelegate shows 2 deletions and 1 insertion! Oddly enough, this doesn't happen 100% of the time. If my UITableView is showing 10 RealmWidget objects and I delete them one at a time, about 75% of the time, I see the above output in the console. But the other 25% of the time, the output is exactly what I was expecting to see:

widget deletion count (AppDelegate): 1
widget insertion count (AppDelegate): 0
widget modification count (AppDelegate): 0

widget deletion count (View controller): 1
widget insertion count (View controller): 0
widget modification count (View controller): 0

1 deletion in both observation blocks. I'm even more confused now than I was before! ;-)

-- EDIT #2 --

It appears that this behavior is related to the fact that the Results<RealmWidget> in my AppDelegate and the Results<RealmWidget> in my view controller are not ordering the query results in the same way.

In my AppDelegate, results is an unsorted collection of all the RealmWidget objects in the Realm. But in my view controller, the helper method that I use to define results is a sorted collection of all the RealmWidgets objects in the Realm.

like image 518
bmt22033 Avatar asked Nov 08 '18 15:11

bmt22033


1 Answers

I posted this question to Realm via GitHub and got the following response:

Yes, unsorted results are rearranged when objects are deleted. If you don't ask for an explicit sort order, then the objects are simply listed in the order that they happen to be stored on disk, and that is an unstable order that changes when objects are deleted.

We currently report the object at the end moving to the place of a deleted object as that object also being deleted and then inserted in the new location.

https://github.com/realm/realm-cocoa/issues/6130

like image 181
bmt22033 Avatar answered Sep 20 '22 11:09

bmt22033