Skip to content

Commit 4cf3b1e

Browse files
committed
add support for batching updates
1 parent 4b7a264 commit 4cf3b1e

File tree

7 files changed

+117
-14
lines changed

7 files changed

+117
-14
lines changed

FirebaseDatabaseUI/FUIArray.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ NS_ASSUME_NONNULL_BEGIN
4343
/**
4444
* FUIArray provides an array structure that is synchronized with a Firebase reference or
4545
* query. It is useful for building custom data structures or sources, and provides the base for
46-
* FirebaseDataSource.
46+
* FirebaseDataSource. FUIArray maintains a large amount of internal state, and most of its methods
47+
* are not thread-safe.
4748
*/
4849
@interface FUIArray : NSObject <FUICollection>
4950

FirebaseDatabaseUI/FUIArray.m

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ @interface FUIArray ()
3333
*/
3434
@property (strong, nonatomic) NSMutableSet<NSNumber *> *handles;
3535

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

3846
@implementation FUIArray
@@ -68,53 +76,84 @@ - (void)dealloc {
6876
#pragma mark - Private API methods
6977

7078
- (void)observeQuery {
71-
if (self.handles.count == 4) { /* don't duplicate observers */ return; }
79+
if (self.handles.count == 5) { /* don't duplicate observers */ return; }
7280
FIRDatabaseHandle handle;
7381
handle = [self.query observeEventType:FIRDataEventTypeChildAdded
7482
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
83+
[self didUpdate];
7584
[self insertSnapshot:snapshot withPreviousChildKey:previousChildKey];
7685
}
7786
withCancelBlock:^(NSError *error) {
78-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
79-
[self.delegate array:self queryCancelledWithError:error];
80-
}
87+
[self raiseError:error];
8188
}];
8289
[_handles addObject:@(handle)];
8390

8491
handle = [self.query observeEventType:FIRDataEventTypeChildChanged
8592
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
93+
[self didUpdate];
8694
[self changeSnapshot:snapshot withPreviousChildKey:previousChildKey];
8795
}
8896
withCancelBlock:^(NSError *error) {
89-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
90-
[self.delegate array:self queryCancelledWithError:error];
91-
}
97+
[self raiseError:error];
9298
}];
9399
[_handles addObject:@(handle)];
94100

95101
handle = [self.query observeEventType:FIRDataEventTypeChildRemoved
96102
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousSiblingKey) {
103+
[self didUpdate];
97104
[self removeSnapshot:snapshot withPreviousChildKey:previousSiblingKey];
98105
}
99106
withCancelBlock:^(NSError *error) {
100-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
101-
[self.delegate array:self queryCancelledWithError:error];
102-
}
107+
[self raiseError:error];
103108
}];
104109
[_handles addObject:@(handle)];
105110

106111
handle = [self.query observeEventType:FIRDataEventTypeChildMoved
107112
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
113+
[self didUpdate];
108114
[self moveSnapshot:snapshot withPreviousChildKey:previousChildKey];
109115
}
110116
withCancelBlock:^(NSError *error) {
111-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
112-
[self.delegate array:self queryCancelledWithError:error];
113-
}
117+
[self raiseError:error];
118+
}];
119+
[_handles addObject:@(handle)];
120+
121+
handle = [self.query observeEventType:FIRDataEventTypeValue
122+
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
123+
[self didFinishUpdates];
124+
}
125+
withCancelBlock:^(NSError *error) {
126+
[self raiseError:error];
114127
}];
115128
[_handles addObject:@(handle)];
116129
}
117130

131+
// Must be called from every non-value event listener in order to work correctly.
132+
- (void)didUpdate {
133+
if (self.isSendingUpdates) {
134+
return;
135+
}
136+
self.isSendingUpdates = YES;
137+
if ([self.delegate respondsToSelector:@selector(arrayDidBeginUpdates:)]) {
138+
[self.delegate arrayDidBeginUpdates:self];
139+
}
140+
}
141+
142+
// Must be called from a value event listener.
143+
- (void)didFinishUpdates {
144+
if (!self.isSendingUpdates) { /* This is probably an error */ return; }
145+
self.isSendingUpdates = NO;
146+
if ([self.delegate respondsToSelector:@selector(arrayDidEndUpdates:)]) {
147+
[self.delegate arrayDidEndUpdates:self];
148+
}
149+
}
150+
151+
- (void)raiseError:(NSError *)error {
152+
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
153+
[self.delegate array:self queryCancelledWithError:error];
154+
}
155+
}
156+
118157
- (void)invalidate {
119158
for (NSNumber *handle in _handles) {
120159
[_query removeObserverWithHandle:handle.unsignedIntegerValue];

FirebaseDatabaseUI/FUICollection.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ NS_ASSUME_NONNULL_BEGIN
6868

6969
@optional
7070

71+
/**
72+
* Called before any other events are sent.
73+
*/
74+
- (void)arrayDidBeginUpdates:(id<FUICollection>)collection;
75+
76+
/**
77+
* Called after all updates have finished.
78+
*/
79+
- (void)arrayDidEndUpdates:(id<FUICollection>)collection;
80+
7181
/**
7282
* Delegate method which is called whenever an object is added to an FUIArray.
7383
* On a FUIArray synchronized to a Firebase reference, this corresponds to an

FirebaseDatabaseUITests/FUIArrayTest.m

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,4 +443,37 @@ - (void)testArrayMovesElementToStartWithNilPreviousKey {
443443
XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback");
444444
}
445445

446+
- (void)testArraySendsMessageBeforeAnyUpdates {
447+
__block NSInteger started = 0;
448+
self.arrayDelegate.didStartUpdates = ^{
449+
started++; // expect this to only ever be incremented once.
450+
};
451+
[self.observable populateWithCount:10];
452+
453+
XCTAssert(started == 1, @"expected array to start updates exactly once");
454+
455+
// Send a value event to mark the end of batch updates.
456+
[self.observable sendEvent:FIRDataEventTypeValue withObject:nil previousKey:nil error:nil];
457+
}
458+
459+
- (void)testArraySendsMessagesAfterReceivingValueEvent {
460+
__block NSInteger started = 0;
461+
self.arrayDelegate.didStartUpdates = ^{
462+
started++; // expect this to only ever be incremented once.
463+
};
464+
[self.observable populateWithCount:10];
465+
466+
XCTAssert(started == 1, @"expected array to start updates exactly once");
467+
468+
__block NSInteger ended = 0;
469+
self.arrayDelegate.didEndUpdates = ^{
470+
ended++;
471+
};
472+
473+
// Send a value event to mark the end of batch updates.
474+
[self.observable sendEvent:FIRDataEventTypeValue withObject:nil previousKey:nil error:nil];
475+
476+
XCTAssert(ended == 1, @"expected array to end updates exactly once");
477+
}
478+
446479
@end

FirebaseDatabaseUITests/FUIDatabaseTestUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ NS_ASSUME_NONNULL_BEGIN
9292
@end
9393

9494
@interface FUIArrayTestDelegate : NSObject <FUICollectionDelegate>
95+
@property (nonatomic, copy) void (^didStartUpdates)();
96+
@property (nonatomic, copy) void (^didEndUpdates)();
9597
@property (nonatomic, copy) void (^queryCancelled)(id<FUICollection> array, NSError *error);
9698
@property (nonatomic, copy) void (^didAddObject)(id<FUICollection> array, id object, NSUInteger index);
9799
@property (nonatomic, copy) void (^didChangeObject)(id<FUICollection> array, id object, NSUInteger index);

FirebaseDatabaseUITests/FUIDatabaseTestUtils.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,18 @@ - (void)populateWithCount:(NSUInteger)count {
218218

219219
@implementation FUIArrayTestDelegate
220220

221+
- (void)arrayDidBeginUpdates:(id<FUICollection>)collection {
222+
if (self.didStartUpdates != NULL) {
223+
self.didStartUpdates();
224+
}
225+
}
226+
227+
- (void)arrayDidEndUpdates:(id<FUICollection>)collection {
228+
if (self.didEndUpdates != NULL) {
229+
self.didEndUpdates();
230+
}
231+
}
232+
221233
- (void)array:(id<FUICollection>)array didAddObject:(id)object atIndex:(NSUInteger)index {
222234
if (self.didAddObject != NULL) {
223235
self.didAddObject(array, object, index);

FirebaseUI.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
8D01FC7B1DAEC2FF00BD7848 /* FUIIndexCollectionViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D01FC7A1DAEC2FF00BD7848 /* FUIIndexCollectionViewDataSourceTest.m */; };
1111
8D1E107C1DAEB97300AEFCA0 /* FUIIndexTableViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D1E107B1DAEB97300AEFCA0 /* FUIIndexTableViewDataSourceTest.m */; };
1212
8D2A84AA1D678B2B0058DF04 /* FirebaseUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D2A84A91D678B2B0058DF04 /* FirebaseUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
13+
8D306BBF1E1C64E200A13B0E /* FUIArrayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA941E91D67951B00CD3685 /* FUIArrayTest.m */; };
14+
8D306BC01E1C64F000A13B0E /* FUICollectionViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA941EC1D67951B00CD3685 /* FUICollectionViewDataSourceTest.m */; };
15+
8D306BC11E1C64F200A13B0E /* FUITableViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DA941ED1D67951B00CD3685 /* FUITableViewDataSourceTest.m */; };
1316
8D3A120E1DC2B122007558BA /* FUISortedArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D3A120C1DC2B122007558BA /* FUISortedArray.h */; };
1417
8D3A120F1DC2B122007558BA /* FUISortedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D3A120D1DC2B122007558BA /* FUISortedArray.m */; };
1518
8D78AF061D9D8CB000CFA9C5 /* UIImageView+FirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D7AD9B61D9317FB006866B9 /* UIImageView+FirebaseStorage.m */; };
@@ -1919,6 +1922,9 @@
19191922
isa = PBXSourcesBuildPhase;
19201923
buildActionMask = 2147483647;
19211924
files = (
1925+
8D306BC01E1C64F000A13B0E /* FUICollectionViewDataSourceTest.m in Sources */,
1926+
8D306BC11E1C64F200A13B0E /* FUITableViewDataSourceTest.m in Sources */,
1927+
8D306BBF1E1C64E200A13B0E /* FUIArrayTest.m in Sources */,
19221928
8D01FC7B1DAEC2FF00BD7848 /* FUIIndexCollectionViewDataSourceTest.m in Sources */,
19231929
8D1E107C1DAEB97300AEFCA0 /* FUIIndexTableViewDataSourceTest.m in Sources */,
19241930
8D924C611DA6F69100C4DA48 /* FUIIndexArrayTest.m in Sources */,

0 commit comments

Comments
 (0)