Realm-cocoa: Avoid triggering change notifications for setting properties to existing values for createOrUpdate

Created on 27 Apr 2016  Â·  24Comments  Â·  Source: realm/realm-cocoa

Goals

I have a json file that contains an array of objects whose model is exactly the same as my RLMObject model so I would like to use -createOrUpdateInDefaultRealmWithValue: or -createOrUpdateInRealm:withValue: to automatically update RLMObject with values from that json every time it's loaded.

Expected Results

If NSDictionary from a json has all the same values as an existing RLMObject, -createOrUpdateInDefaultRealmWithValue: and -createOrUpdateInRealm:withValue: should not cause change notifications to be triggered for that object.

Actual Results

Even if json has all the same values as an existing RLMObject, -createOrUpdateInDefaultRealmWithValue: and -createOrUpdateInRealm:withValue: will cause change notifications to be triggered for every object even when its "update" causes no actual value updates.

I know that's probably caused by KVO which fires no matter if new value is the same, but is there any way you could check the old value before setting it so that it doesn't trigger? I can and will do this manually like I did with Core Data, but it would be really nice if it was done automatically because I can't imagine a scenario where I would want to be notified when RLMObject's properties are assigned to the same values as they were before.

Version of Realm and Tooling

Realm version: 0.99.1

Breaking O-Community P-2-Expected T-Enhancement

Most helpful comment

When are you planning for fixing this. Actually i use realm for cache purpose. So lets say i have a feed Object which contains a user object and lets say i have tab bar and on each tab i have pushed 2-3 controllers. Now i also have comments, explore, chat and board object which also has user object. Now lets say i refresh feed but gets data that is already in realm. As no data is new but since the realm.create with update true has been fired it will generate notification for that user and as this user belongs to other pages as well, nearly 300-400 modification change gets fired without any actual update. This is currently producing some lag in my application. I am worried what will happen if user tries to go deep in the app, the modification call will be 2-3 times the current call.

All 24 comments

The collection change payload that's included in notification blocks should only be considering changes to the values, and ignoring properties being set to their current value, so this would be a bug.

Could you please share the actual relevant information here? Like your models, JSON input data, previous values for these models prior to the update, code to reproduce this? Thanks!

Thanks for fast response. I didn't think it was a bug because I could reproduce it in a new project with minimal json. I uploaded a sample project that you can check out: https://github.com/fichek/RLMIssue3489

Hitting import button for the first time ever logs 3 insertions as expected, but hitting it again after that shows 3 modifications every time even though json file is static.

screen shot or sample project logs

@jpsim can you confirm yet if this is a bug or intended? Thanks.

Here's how I hacked around it for now if anyone needs a quick and dirty solution to this while we wait for a proper official solution.

I'll need to spend the proper time to investigate in order to get back to you on that. I'll post here once I've done that.

I created a category with drop-in replacement method for +createOrUpdateInRealm:withValue: that works around this issue. What it does is use +createOrUpdateInRealm:withValue: but only provides it with primary key value and then tries to do the rest of updating only if there is a different value than the one already stored in object. It's still a bit hacky, but it works and is not invasive so I'm ok with it until official fix :)

Sample project was also updated with additional button that imports using that method so you can try it out quickly.

Any news on your side?

No, no news on my side, but the project you've created to me looks to be an issue on our side. That should be helpful in figuring out what's happening, if it is indeed a bug, and if so how we can fix it.

+1

I would expect an .Update notification only if an object's data has actually changed, not simply updated with equal data. I was actually surprised to learn that a notification was sent at all, which I'd consider a bug (unless something non-obvious and necessary happens under the hood). We often use notifications to update UI, hence this leads to unintended UI changes.

If this is expected behaviour, I think the documentation should be updated accordingly. The Realm Swift Documentation (section Notifications) states that:

You’re notified about modifications whenever a property of an object has changed

The scenario that this issue describes does not correspond to what is outlined in the documentation.

While investigating this in my own project (before reading this issue), I discovered some other behaviour that I don't think fully corresponds with what is described in the documentation. Consider an object with a primary key and another property:

class MyObject: Object {
   dynamic var id = 0
   dynamic var data = ""

   convenience init(id: Int) {
      self.init()
      self.id = id
   }

   override class func primaryKey() -> String? {
      return "id"
   }
}

The following does _not_ trigger an .Update notification (as expected):

  1. Create an instance of MyObject
  2. Add it to a realm using add(object)
  3. Add the same instance into the same realm using add(object, update: true)
// Assume token is stored and realm is created
token = realm.objects(MyObject).addNotificationBlock { (changes: RealmCollectionChange) in
   switch changes {
   case .Update:
      print("Updated!")
      break
   default:
      break
   }
}
let object = MyObject(id: 1)
try! realm.write {
   realm.add(object)
}
try! realm.write {
   realm.add(object, update: true)
}

If the data property is changed before running realm.add(object, update: true), an .Update notification is triggered (again, as expected). However, the following _does_ trigger an .Update notification:

  1. Repeat 1 and 2 above
  2. Create a new instance of MyObject with the same primary key
  3. Insert the new instance into the same realm using add(object, update: true)
let sameObject = MyObject(id: 1)
try! realm.write {
   realm.add(sameObject, update: true)
}

This is often what happens when fetching data from a server and updating the database (hence the origin of this issue). What I find odd though, is revealed when comparing instances of MyObject using isEqual (source):

let object = MyObject(id: 1) 
object.isEqual(object) // true (as expected)

let sameObject = MyObject(id: 1)
object.isEqual(sameObject) // false; they are not attached to the same realm / one of them is detached (according to isEqual's implementation)

try! realm.write {
   realm.add(sameObject, update: true)
}
object.isEqual(sameObject) // true

In other words, the two instances of MyObject are considered eventually. From my understanding, this is because they are in the same table in the database, at the same index (because of the identical primary key). And since equality is determined correctly, I find it even more surprising that the .Update notification is sent in this case.

I hope this can help you figure out what is happening and whether it is indeed expected behaviour.

Hi Guys, I´m having the same problem. @jpsim Do you have any news on your side? Are you planning to fix this issue?

Thanks, Juan

No news on our side, but I'd like to reiterate that I (and at least a few other people) at Realm consider the current behavior to be suboptimal and would like to implement the changes proposed in this thread. But we don't have concrete plans to do that right away, hence the prioritization level of S:P2 Backlog indicated by the label on this issue.

Thanks for the update @jpsim.

When are you planning for fixing this. Actually i use realm for cache purpose. So lets say i have a feed Object which contains a user object and lets say i have tab bar and on each tab i have pushed 2-3 controllers. Now i also have comments, explore, chat and board object which also has user object. Now lets say i refresh feed but gets data that is already in realm. As no data is new but since the realm.create with update true has been fired it will generate notification for that user and as this user belongs to other pages as well, nearly 300-400 modification change gets fired without any actual update. This is currently producing some lag in my application. I am worried what will happen if user tries to go deep in the app, the modification call will be 2-3 times the current call.

I'm also very interested in the timeline of this fix. I have a similar scenario as @jovanpreet. Would like to only be notified of objects who's properties (1 or more) have actually changed.

Got this also.
This is triggering a reload of my tableview, calling the imageCache again making the images blink.
Any workaround on that?

@tirrorex @aottenwess @jovanpreet
Guys, this is fixed, right?
You get a notification every time you modify the model, but you get the oldValue and the newValue, so you can compare it...
Like you want to update the tableviewCell when its model change, simply subscribe for model changes, compare the old and new value.

Or am I misunderstand something about your problems?

@freeubi there is no modification at all on the model, all the values are still the same.
So there is no reason to actually check this on the tableviewCell since we shouldn't receive a modification notification in the first place.
I don't know if it is fixed though, we just finished the swift 3 migration and i haven't had the time to dig into this.

Currently, you need write few lines of code for the "modification-only-update" functionality, and yes, there is a little performance overhead (because of the unnecessary notification sending).
But you can get around easily, so I don't see this as a huge problem.

To be clear, I think that this will be a good thing in Realm, but not must-have and you will get just a little more performance with it.

What you think and what we need are not necessarily the same thing mate.
Doing this manually require to check every property of every object from every model for each object created, this is more than "a little performance overhead" and "few lines of code". (unless i missed an obvious way to check the equality between two objects easily, it seems the easiest solution is not working as expected thus is not reliable)

@freeubi
As far as i know this oldValue and newValue only works on RLMObject and not on RLMArray and also the oldValue is null if notificationBlock and realm.create are on same thread.

@jovanpreet yes, only works on object (but a list contains that), and you need to subscribe and modify on different thread.

All i do is subscribe on the cell for Object change Notification, and the database service who insert/update the data is working on a background thread.

@freeubi I couldn't agree more with @tirrorex, this is definitely NOT implemented yet and it's a LOT of unnecessary pollution – and of course super error-prone and hard to trace if you missed some newly-added property somewhere. This is a cross-cutting concern and something the framework should be handling itself.

This is especially important for server synching where you can get object updates that only change a specific property, yet you apply the changes to an object all at once (either by applying the JSON or materializing a new object and doing an upsert - ie: createOrUpdate). Thus to have a production-ready, real-time application (the core value prop of Realm) you NEED to have this in the framework.

Issue NOT fixed. ETA on it coming? Common guys...

I originally was creating my objects with out a primary key and parsing updates for my objects from my returned JOSN from the server. I would then apply these updates with out discrimination and noticed way to many modifications being passed through in my addNotificationBlock (On a List<Object> of the highest level objects in my data graph). My fault I told myself. I went and changed all my realm objects to have a primary key and passed updates using the realm.add(self, update: true) method.

I was still getting all the notifications!! I'm glad I found this thread. I simply stopped reloading my UITableViewCell's and I can manage the ~3 second spike of CPU usage. When reloading the UITableViewCell's the CPU was pegged at 100% for a few seconds.

I guess what I'm trying to say is that I would love it if Realm did actually only send notifications for actual changes. If not, I guess I can propagate my own custom notifications on the server sync's.

I have this same problem!

You can see:

https://github.com/pabloruan0710/realm-notification

Add Notification:

fileprivate func setupNotification(){
        token = Pessoas.addNotificationBlock({(changes: RealmCollectionChange) in
            switch changes {
            case .initial(_):
                print("Inicial")
            case .update(_, deletions:_, insertions: _, modifications: let modificados):
                print("\nModificados: ")
                print(modificados)
            case .error(_):
                fatalError("crash")
            }
            print("\n")
            print(changes)
        })
    }
 fileprivate func novaPessoa(){

        let realm = try! Realm()
        let newPeople = Pessoa()
        newPeople.nome = "Pablo"
        newPeople.id = 1
        try? realm.write {
            realm.add(newPeople, update: true)
        }

        let cachePeople = Pessoa()
        cachePeople.nome = "Pablo"
        cachePeople.id = 1
        try? realm.write {
            realm.add(cachePeople, update: true)
        }

        let cachePeople1 = Pessoa()
        cachePeople1.nome = "Pablo"
        cachePeople1.id = 1
        try? realm.write {
            realm.add(cachePeople1, update: true)
        }

        let cachePeople2 = Pessoa()
        cachePeople2.nome = "Pablo"
        cachePeople2.id = 1
        try? realm.write {
            realm.add(cachePeople2, update: true)
        }
    }



md5-8567053a1ef95a04fd48595d90a2bf9c



> initial(Results
> 
>  <0x10261e5c0> ( [0] Pessoa { id = 1; nome = Pablo; } ))
> 
> Modificados: [0]
> 
> update(Results <0x10261e5c0> ( [0] Pessoa { id = 1; nome = Pablo; } ), deletions: [], insertions: [], modifications: [0])
> 
> Modificados: [0]
> 
> update(Results <0x10261e5c0> ( [0] Pessoa { id = 1; nome = Pablo; } ), deletions: [], insertions: [], modifications: [0])
> 
> Modificados: [0]
> 
> update(Results <0x10261e5c0> ( [0] Pessoa { id = 1; nome = Pablo; } ), deletions: [], insertions: [], modifications: [0])
> 
> Modificados: [0]
> 
> update(Results <0x10261e5c0> ( [0] Pessoa { id = 1; nome = Pablo; } ), deletions: [], insertions: [], modifications: [0])

We are aware of the desire for a fix to this issue, have decided that this fix is, for architectural reasons, best implemented in the database core, and have taken steps accordingly. Any further updates on this matter will be posted to this ticket.

The core issue tracking this topic is https://github.com/realm/realm-core/issues/2787.

This was released in 3.16.0. realm.create(type, value, update: .modified) in Swift or [Type createOrUpdateModified:value inRealm:realm] in obj-c will only set the properties which have changed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

i-schuetz picture i-schuetz  Â·  3Comments

xspyhack picture xspyhack  Â·  3Comments

yangmeyer picture yangmeyer  Â·  3Comments

menhui222 picture menhui222  Â·  3Comments

TheHmmka picture TheHmmka  Â·  3Comments