Skip to content

Commit

Permalink
add support for batching updates
Browse files Browse the repository at this point in the history
  • Loading branch information
morganchen12 committed Jan 3, 2017
1 parent 4b7a264 commit 4cf3b1e
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 14 deletions.
3 changes: 2 additions & 1 deletion FirebaseDatabaseUI/FUIArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ NS_ASSUME_NONNULL_BEGIN
/**
* FUIArray provides an array structure that is synchronized with a Firebase reference or
* query. It is useful for building custom data structures or sources, and provides the base for
* FirebaseDataSource.
* FirebaseDataSource. FUIArray maintains a large amount of internal state, and most of its methods
* are not thread-safe.
*/
@interface FUIArray : NSObject <FUICollection>

Expand Down
65 changes: 52 additions & 13 deletions FirebaseDatabaseUI/FUIArray.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ @interface FUIArray ()
*/
@property (strong, nonatomic) NSMutableSet<NSNumber *> *handles;

/**
* Set to YES when any event that isn't a value event is received; set
* back to NO when receiving a value event.
* Used to keep track of whether or not the array is updating so consumers
* can more easily batch updates.
*/
@property (nonatomic, assign) BOOL isSendingUpdates;

@end

@implementation FUIArray
Expand Down Expand Up @@ -68,53 +76,84 @@ - (void)dealloc {
#pragma mark - Private API methods

- (void)observeQuery {
if (self.handles.count == 4) { /* don't duplicate observers */ return; }
if (self.handles.count == 5) { /* don't duplicate observers */ return; }
FIRDatabaseHandle handle;
handle = [self.query observeEventType:FIRDataEventTypeChildAdded
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
[self didUpdate];
[self insertSnapshot:snapshot withPreviousChildKey:previousChildKey];
}
withCancelBlock:^(NSError *error) {
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
[self.delegate array:self queryCancelledWithError:error];
}
[self raiseError:error];
}];
[_handles addObject:@(handle)];

handle = [self.query observeEventType:FIRDataEventTypeChildChanged
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
[self didUpdate];
[self changeSnapshot:snapshot withPreviousChildKey:previousChildKey];
}
withCancelBlock:^(NSError *error) {
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
[self.delegate array:self queryCancelledWithError:error];
}
[self raiseError:error];
}];
[_handles addObject:@(handle)];

handle = [self.query observeEventType:FIRDataEventTypeChildRemoved
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousSiblingKey) {
[self didUpdate];
[self removeSnapshot:snapshot withPreviousChildKey:previousSiblingKey];
}
withCancelBlock:^(NSError *error) {
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
[self.delegate array:self queryCancelledWithError:error];
}
[self raiseError:error];
}];
[_handles addObject:@(handle)];

handle = [self.query observeEventType:FIRDataEventTypeChildMoved
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
[self didUpdate];
[self moveSnapshot:snapshot withPreviousChildKey:previousChildKey];
}
withCancelBlock:^(NSError *error) {
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
[self.delegate array:self queryCancelledWithError:error];
}
[self raiseError:error];
}];
[_handles addObject:@(handle)];

handle = [self.query observeEventType:FIRDataEventTypeValue
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
[self didFinishUpdates];
}
withCancelBlock:^(NSError *error) {
[self raiseError:error];
}];
[_handles addObject:@(handle)];
}

// Must be called from every non-value event listener in order to work correctly.
- (void)didUpdate {
if (self.isSendingUpdates) {
return;
}
self.isSendingUpdates = YES;
if ([self.delegate respondsToSelector:@selector(arrayDidBeginUpdates:)]) {
[self.delegate arrayDidBeginUpdates:self];
}
}

// Must be called from a value event listener.
- (void)didFinishUpdates {
if (!self.isSendingUpdates) { /* This is probably an error */ return; }
self.isSendingUpdates = NO;
if ([self.delegate respondsToSelector:@selector(arrayDidEndUpdates:)]) {
[self.delegate arrayDidEndUpdates:self];
}
}

- (void)raiseError:(NSError *)error {
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
[self.delegate array:self queryCancelledWithError:error];
}
}

- (void)invalidate {
for (NSNumber *handle in _handles) {
[_query removeObserverWithHandle:handle.unsignedIntegerValue];
Expand Down
10 changes: 10 additions & 0 deletions FirebaseDatabaseUI/FUICollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ NS_ASSUME_NONNULL_BEGIN

@optional

/**
* Called before any other events are sent.
*/
- (void)arrayDidBeginUpdates:(id<FUICollection>)collection;

/**
* Called after all updates have finished.
*/
- (void)arrayDidEndUpdates:(id<FUICollection>)collection;

/**
* Delegate method which is called whenever an object is added to an FUIArray.
* On a FUIArray synchronized to a Firebase reference, this corresponds to an
Expand Down
33 changes: 33 additions & 0 deletions FirebaseDatabaseUITests/FUIArrayTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,37 @@ - (void)testArrayMovesElementToStartWithNilPreviousKey {
XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback");
}

- (void)testArraySendsMessageBeforeAnyUpdates {
__block NSInteger started = 0;
self.arrayDelegate.didStartUpdates = ^{
started++; // expect this to only ever be incremented once.
};
[self.observable populateWithCount:10];

XCTAssert(started == 1, @"expected array to start updates exactly once");

// Send a value event to mark the end of batch updates.
[self.observable sendEvent:FIRDataEventTypeValue withObject:nil previousKey:nil error:nil];
}

- (void)testArraySendsMessagesAfterReceivingValueEvent {
__block NSInteger started = 0;
self.arrayDelegate.didStartUpdates = ^{
started++; // expect this to only ever be incremented once.
};
[self.observable populateWithCount:10];

XCTAssert(started == 1, @"expected array to start updates exactly once");

__block NSInteger ended = 0;
self.arrayDelegate.didEndUpdates = ^{
ended++;
};

// Send a value event to mark the end of batch updates.
[self.observable sendEvent:FIRDataEventTypeValue withObject:nil previousKey:nil error:nil];

XCTAssert(ended == 1, @"expected array to end updates exactly once");
}

@end
2 changes: 2 additions & 0 deletions FirebaseDatabaseUITests/FUIDatabaseTestUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ NS_ASSUME_NONNULL_BEGIN
@end

@interface FUIArrayTestDelegate : NSObject <FUICollectionDelegate>
@property (nonatomic, copy) void (^didStartUpdates)();
@property (nonatomic, copy) void (^didEndUpdates)();
@property (nonatomic, copy) void (^queryCancelled)(id<FUICollection> array, NSError *error);
@property (nonatomic, copy) void (^didAddObject)(id<FUICollection> array, id object, NSUInteger index);
@property (nonatomic, copy) void (^didChangeObject)(id<FUICollection> array, id object, NSUInteger index);
Expand Down
12 changes: 12 additions & 0 deletions FirebaseDatabaseUITests/FUIDatabaseTestUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,18 @@ - (void)populateWithCount:(NSUInteger)count {

@implementation FUIArrayTestDelegate

- (void)arrayDidBeginUpdates:(id<FUICollection>)collection {
if (self.didStartUpdates != NULL) {
self.didStartUpdates();
}
}

- (void)arrayDidEndUpdates:(id<FUICollection>)collection {
if (self.didEndUpdates != NULL) {
self.didEndUpdates();
}
}

- (void)array:(id<FUICollection>)array didAddObject:(id)object atIndex:(NSUInteger)index {
if (self.didAddObject != NULL) {
self.didAddObject(array, object, index);
Expand Down
6 changes: 6 additions & 0 deletions FirebaseUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
8D01FC7B1DAEC2FF00BD7848 /* FUIIndexCollectionViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D01FC7A1DAEC2FF00BD7848 /* FUIIndexCollectionViewDataSourceTest.m */; };
8D1E107C1DAEB97300AEFCA0 /* FUIIndexTableViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D1E107B1DAEB97300AEFCA0 /* FUIIndexTableViewDataSourceTest.m */; };
8D2A84AA1D678B2B0058DF04 /* FirebaseUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D2A84A91D678B2B0058DF04 /* FirebaseUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
8D306BBF1E1C64E200A13B0E /* FUIArrayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA941E91D67951B00CD3685 /* FUIArrayTest.m */; };
8D306BC01E1C64F000A13B0E /* FUICollectionViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA941EC1D67951B00CD3685 /* FUICollectionViewDataSourceTest.m */; };
8D306BC11E1C64F200A13B0E /* FUITableViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA941ED1D67951B00CD3685 /* FUITableViewDataSourceTest.m */; };
8D3A120E1DC2B122007558BA /* FUISortedArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D3A120C1DC2B122007558BA /* FUISortedArray.h */; };
8D3A120F1DC2B122007558BA /* FUISortedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D3A120D1DC2B122007558BA /* FUISortedArray.m */; };
8D78AF061D9D8CB000CFA9C5 /* UIImageView+FirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D7AD9B61D9317FB006866B9 /* UIImageView+FirebaseStorage.m */; };
Expand Down Expand Up @@ -1919,6 +1922,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D306BC01E1C64F000A13B0E /* FUICollectionViewDataSourceTest.m in Sources */,
8D306BC11E1C64F200A13B0E /* FUITableViewDataSourceTest.m in Sources */,
8D306BBF1E1C64E200A13B0E /* FUIArrayTest.m in Sources */,
8D01FC7B1DAEC2FF00BD7848 /* FUIIndexCollectionViewDataSourceTest.m in Sources */,
8D1E107C1DAEB97300AEFCA0 /* FUIIndexTableViewDataSourceTest.m in Sources */,
8D924C611DA6F69100C4DA48 /* FUIIndexArrayTest.m in Sources */,
Expand Down

0 comments on commit 4cf3b1e

Please sign in to comment.