realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.32k stars 2.15k forks source link

Best practices for updating existing objects #4360

Closed alexeychirkov closed 7 years ago

alexeychirkov commented 7 years ago

Goals

The goal is to NOT receive a "modification" change when existing RLMObject with primaryKey is updated with the same property value as it has (e.g. the same string)

Expected Results

"Modification" change is not received

Actual Results

"Modification" change is received

Steps to Reproduce

See below

Code Sample

Assume that we have existing TestEntity with primaryKey 'testKey' and name 'testName'.

TestEntity

@interface TestEntity : RLMObject
@property NSString *primaryKey;
@property NSString *name;
@end

@implementation TestEntity
+ (NSString *)primaryKey {
    return @"primaryKey";
}
@end

ViewController

...
@interface ViewController ()
@property (nonatomic) RLMNotificationToken *notification;
@end

@implementation HomeViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //...
    @weakify(self);
    self.notification = [[[TestEntity allObjects] sortedResultsUsingProperty:@"name" ascending:YES] addNotificationBlock:^(RLMResults *data, RLMCollectionChange *changes, NSError *error) {
        @strongify(self);
        if (error) {
            //handle error
            return;
        }

        if (!changes) {
            //reload tableView
            return;
        }

        NSLog(@"insertions [%@], deletions [%@], modifications [%@]", [changes.insertions componentsJoinedByString:@","], [changes.deletions componentsJoinedByString:@","], [changes.modifications componentsJoinedByString:@","]);
    }];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        RLMRealm *realm = [RLMRealm defaultRealm];
        [realm beginWriteTransaction];
        //get existing TestEntity
        TestEntity *existing = [TestEntity objectForPrimaryKey:@"testKey"];
        //will output "existing entity: primaryKey = 'testKey', name = 'testName'"
        NSLog(@"existing entity: primaryKey = '%@', name = '%@'", existing.primaryKey, existing.name);
        //try to update existing entity with the same "name" value
        [TestEntity createOrUpdateInRealm:realm withValue:@{@"primaryKey": @"testKey", @"name": @"testName"}];
        [realm commitWriteTransaction];
    });
}

@end

As a result I get the following change output: insertions [], deletions [], modifications [0]

I am confused, because property had the same value - it means that object was not changed. Receiving "false positive" changes leads to unnecessary UI redraw (e.g. redraw of the tableView cell).

So we have two methods: addOrUpdateObject and createOrUpdateInRealm. Is the following is true?

But what about createOrUpdateInRealm method?

How to achieve my goal? Lets say I am refreshing the list of TestEntities from the server and want to store (update) this list to Realm. List is already stored in Realm, so it is possible that all entities are the same, list in sync already. How to update the list without getting "modifications" change?

What I do now:

Version of Realm and Tooling

Realm version: Realm (2.1.0)

Xcode version: 8.1

iOS/OSX version: 10.11.6

Dependency manager + version: Cocoapods 1.0.1

austinzheng commented 7 years ago

Thanks for getting in touch. The behavior described in your ticket (modification notification even when the values are equal) is a known limitation, and we'd very much like to fix it eventually (see https://github.com/realm/realm-cocoa/issues/3489).

alexeychirkov commented 7 years ago

Thanks, I will close this issue