-
Notifications
You must be signed in to change notification settings - Fork 365
YapDatabaseModifiedNotification
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.
There are 2 features that make it dead simple to keep the User Interface in-sync with the data layer.
- Long-Lived Read Transactions
- 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.
A YapDatabaseModifiedNotification allows you to see what changed in a particular commit. For example:
- (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 of YapDatabase throughout the rest of your app, 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.
Both YapDatabaseConnection & YapCollectionsDatabaseConnection have a number of API's to inspect YapDatabaseModifiedNotification's.
From YapDatabaseConnection.h:
- (BOOL)hasChangeForKey:(NSString *)key inNotifications:(NSArray *)notifications;
- (BOOL)hasObjectChangeForKey:(NSString *)key inNotifications:(NSArray *)notifications;
- (BOOL)hasMetadataChangeForKey:(NSString *)key inNotifications:(NSArray *)notifications;
- (BOOL)hasChangeForAnyKeys:(NSSet *)key inNotifications:(NSArray *)notifications;
- (BOOL)hasObjectChangeForAnyKeys:(NSSet *)keys inNotifications:(NSArray *)notifications;
- (BOOL)hasMetadataChangeForAnyKeys:(NSSet *)keys inNotifications:(NSArray *)notifications;From YapCollectionsDatabaseConnection.h:
// Query for any change to a collection
- (BOOL)hasChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications;
- (BOOL)hasObjectChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications;
- (BOOL)hasMetadataChangeForCollection:(NSString *)collection inNotifications:(NSArray *)notifications;
// Query for a change to a particular key/collection tuple
- (BOOL)hasChangeForKey:(NSString *)key
inCollection:(NSString *)collection
inNotifications:(NSArray *)notifications;
- (BOOL)hasObjectChangeForKey:(NSString *)key
inCollection:(NSString *)collection
inNotifications:(NSArray *)notifications;
- (BOOL)hasMetadataChangeForKey:(NSString *)key
inCollection:(NSString *)collection
inNotifications:(NSArray *)notifications;
// Query for a change to a particular set of keys in a collection
- (BOOL)hasChangeForAnyKeys:(NSSet *)keys
inCollection:(NSString *)collection
inNotifications:(NSArray *)notifications;
- (BOOL)hasObjectChangeForAnyKeys:(NSSet *)keys
inCollection:(NSString *)collection
inNotifications:(NSArray *)notifications;
- (BOOL)hasMetadataChangeForAnyKeys:(NSSet *)keys
inCollection:(NSString *)collection
inNotifications:(NSArray *)notifications;You'll notice that all the methods take an array of notifications. This is designed to match the NSArray of notifications you get via beginLongLivedReadTransaction and endLongLivedReadTransaction.
The processing code is the same. Internally it doesn't matter if you pass it an array with one notification, or with 10 notifications. Multiple change-sets can always be and treated as one.
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 sorting your data for display in a tableView or collectionView.
And extensions are also integrated into the YapDatabaseModifiedNotification architecture.
In terms of views, this means that you can pass a YapDatabaseModifiedNotification to a view, and it will spit out what changed in terms of the view. That is, it can say something like this:
- The row at index 7 was moved to index 7
- A row was inserted at index 12
- The row at index 4 was deleted
In short, exactly what you need to pass to UITableView / UICollectionView in order to animate changes.
And again, it doesn't matter if you need to jump 1 commit, or 10 commits. No sweat.
- (void)viewDidLoad
{
// Freeze our connection for use on the main-thread.
// This gives us a stable data-source that won't change until we tell it to.
[databaseConnection beginLongLivedReadTransaction];
// The view may have a whole bunch of groups.
// In this example, we just want to look at a single group (1 section).
NSArray *groups = @[ @"bestSellers" ];
mappings = [[YapDatabaseViewMappings alloc] initWithGroups:groups view:@"sales"];
// We can do all kinds of cool stuff with the mappings object.
// See the views article for more information.
//
// Now initialize the mappings object.
// It will fetch and cache the counts per group/section.
[databaseConnection readWithBlock:(YapDatabaseReadTransaction *transaction){
// One-time initialization
[mappings updateWithTransaction:transaction];
}];
// And register for notifications when the database changes.
// Our method will be invoked on the main-thread,
// and will allow us to move our stable data-source from our existing state to an updated state.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:databaseConnection.database];
}
- (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 = [databaseConnection beginLongLivedReadTransaction];
// What changed in my tableView?
NSArray *rowChanges = nil;
[[databaseConnection ext:@"salesRank"] getSectionChanges:NULL
rowChanges:&rowChanges
forNotifications:notifications
withMappings:mappings];
if ([rowChanges 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 (YapDatabaseViewRowChange *rowChange in rowChanges)
{
switch (rowChange.type)
{
case YapDatabaseViewChangeDelete :
{
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case YapDatabaseViewChangeInsert :
{
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case YapDatabaseViewChangeMove :
{
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case YapDatabaseViewChangeUpdate :
{
[self.tableView reloadRowsAtIndexPaths:@[ rowChange.indexPath ]
withRowAnimation:UITableViewRowAnimationNone];
break;
}
}
}
[self.tableView endUpdates];
}