Skip to content

YapDatabaseModifiedNotification

Robbie Hanson edited this page Jun 27, 2013 · 19 revisions

The following article applies to version 2.0+

Answering that age old question: "What's new?"

YapDatabase simplifies many aspects of database development. It provides a simple Objective-C API. It has a straight-forward concurrency model that is a pleasure to use. And it provides a number of other great features such as built-in caching.

But sometimes the database itself isn't the difficult aspect of development. Sometimes it's updating the UI and keeping it in-sync with the underlying data model.

YapDatabase + UI

There are 2 features that make it dead simple to keep the User Interface in-sync with the data layer.

  1. Long-Lived Read Transactions
  2. YapDatabaseModifiedNotification

The first feature is long-lived read transactions. There is a dedicated wiki article for this topic. The rest of this article assumes you've already read the LongLivedReadTransactions article.

The second feature is the YapDatabaseModifiedNotification. These notifications allow you to figure out what changed during any particular read-write transaction.

What changed?

- (void)viewDidLoad
{
    // Freeze my database connection while I draw my views and populate my tableView.
    // Background operations are free to update the database using their own connection.
    [databaseConnection beginLongLivedReadTransaction];

    // Register for notifications of changes to the database.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(yapDatabaseModified:)
                                                 name:YapDatabaseModifiedNotification
                                               object:database];
}

- (void)yapDatabaseModified:(NSNotification *)notification
{
    // Jump to the most recent commit.
    // End & Re-Begin the long-lived transaction atomically.
    // Also grab all the notifications for all the commits that I jump.
    NSArray *notifications = [database beginLongLivedReadTransaction];

    // Update views if needed
    if ([databaseConnection hasChangeForKey:itemId inNotifications:notifications]) {
        [self updateItemView];
    }

    // ...
}

As you can see, these 2 features allow you to write code in a natural manner on the main thread. Even though you have all the power and concurrency that YapDatabase provides, you can easily provide a stable database connection for the main thread. And you can easily manage moving your UI from one state to another.

But wait! There's more!

YapDatabase comes with several cool extensions. One such extension is Views, which allows you to do some pretty cool stuff. Views are particularly helpful when displaying your data in a tableView or collectionView.

And extensions are also integrated into the YapDatabaseModifiedNotification architecture.

If a picture is worth a thousand words, what's the ratio for code?

- (NSInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSInteger)section
{
    __block NSUInteger count = 0;
    [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {

        count = [[transaction extension:@"sales"] numberOfKeysInGroup:@"bestSellers"];
    }];

    return (NSInteger)count;
}

- (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    __block SaleItem *item = nil;
    [databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {

        item = [[transaction extension:@"sales"] objectAtIndex:indexPath.row inGroup:@"bestSellers"];
    }];

    return [self cellForItem:item];
}

- (void)yapDatabaseModified:(NSNotification *)notification
{
    // Jump to the most recent commit.
    // End & Re-Begin the long-lived transaction atomically.
    // Also grab all the notifications for all the commits that I jump.
    NSArray *notifications = [roDatabaseConnection beginLongLivedReadTransaction];

    // What changed in my tableView?

    NSDictionary *mappings = @{ @"bestSellers" : @(0) };
    NSArray *changes = [[databaseConnection extension:@"sales"] operationsForNotifications:notifications
	                                                        withGroupToSectionMappings:mappings];
    if ([changes count] == 0)
    {
        // There aren't any changes that affect our tableView!
        return;
    }

    // Familiar with NSFetchedResultsController?
    // Then this should look pretty familiar

    [self.tableView beginUpdates];
	
    for (YapDatabaseViewOperation *operation in changes)
    {
        switch (operation.type)
        {
            case YapDatabaseViewOperationDelete :
            {
                [self.tableView deleteRowsAtIndexPaths:@[ operation.indexPath ]
                                      withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            }
            case YapDatabaseViewOperationInsert :
            {
                [self.tableView insertRowsAtIndexPaths:@[ operation.newIndexPath ]
                                      withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            }
            case YapDatabaseViewOperationMove :
            {
                [self.tableView deleteRowsAtIndexPaths:@[ operation.indexPath ]
                                      withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView insertRowsAtIndexPaths:@[ operation.newIndexPath ]
                                      withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            }
            case YapDatabaseViewOperationUpdate :
            {
                [self.tableView reloadRowsAtIndexPaths:@[ operation.indexPath ]
                                      withRowAnimation:UITableViewRowAnimationAutomatic];
                break;
            }
        }
    }

    [self.tableView endUpdates];
}

Clone this wiki locally