Skip to content

Commit 5ff4c2d

Browse files
authored
Merge pull request #186 from morganchen12/transforms
Transforms
2 parents d4e939c + c51dec1 commit 5ff4c2d

25 files changed

+889
-223
lines changed

FirebaseDatabaseUI/FUIArray.h

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
@import FirebaseDatabase;
2222

23-
#import "FUIArrayDelegate.h"
23+
#import "FUICollection.h"
2424

2525
NS_ASSUME_NONNULL_BEGIN
2626

@@ -43,30 +43,31 @@ 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
*/
48-
@interface FUIArray : NSObject
49+
@interface FUIArray : NSObject <FUICollection>
4950

5051
/**
5152
* The delegate object that array changes are surfaced to, which conforms to the
52-
* @c FUIArrayDelegate protocol.
53+
* @c FUICollectionDelegate protocol.
5354
*/
54-
@property(weak, nonatomic, nullable) id<FUIArrayDelegate> delegate;
55+
@property (weak, nonatomic, nullable) id<FUICollectionDelegate> delegate;
5556

5657
/**
5758
* The query on a Firebase reference that provides data to populate the array.
5859
*/
59-
@property(strong, nonatomic) id<FUIDataObservable> query;
60+
@property (strong, nonatomic) id<FUIDataObservable> query;
6061

6162
/**
6263
* The number of objects in the array.
6364
*/
64-
@property(nonatomic, readonly) NSUInteger count;
65+
@property (nonatomic, readonly) NSUInteger count;
6566

6667
/**
6768
* The items currently in the array.
6869
*/
69-
@property(nonatomic, readonly, copy) NSArray *items;
70+
@property (nonatomic, readonly, copy) NSArray *items;
7071

7172
#pragma mark - Initializer methods
7273

@@ -78,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
7879
* @return A FirebaseArray instance
7980
*/
8081
- (instancetype)initWithQuery:(id<FUIDataObservable>)query
81-
delegate:(nullable id<FUIArrayDelegate>)delegate NS_DESIGNATED_INITIALIZER;
82+
delegate:(nullable id<FUICollectionDelegate>)delegate NS_DESIGNATED_INITIALIZER;
8283

8384
/**
8485
* Initalizes FirebaseArray with a Firebase query (FIRDatabaseQuery) or database reference
@@ -102,9 +103,9 @@ NS_ASSUME_NONNULL_BEGIN
102103
/**
103104
* Returns an object at a specific index in the array.
104105
* @param index The index of the item to retrieve
105-
* @return The object at the given index
106+
* @return The snapshot at the given index
106107
*/
107-
- (id)objectAtIndex:(NSUInteger)index;
108+
- (FIRDataSnapshot *)snapshotAtIndex:(NSInteger)index;
108109

109110
/**
110111
* Returns a Firebase reference for an object at a specific index in the array.
@@ -135,6 +136,38 @@ NS_ASSUME_NONNULL_BEGIN
135136
*/
136137
- (NSUInteger)indexForKey:(NSString *)key;
137138

139+
/**
140+
* Called when the Firebase query sends a FIRDataEventTypeChildAdded event. Override this
141+
* to provide custom insertion logic. Don't call this method directly.
142+
* @param snap The snapshot that was inserted.
143+
* @param previous The key of the sibling preceding the inserted snapshot.
144+
*/
145+
- (void)insertSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(nullable NSString *)previous;
146+
147+
/**
148+
* Called when the Firebase query sends a FIRDataEventTypeChildRemoved event. Override this
149+
* to provide custom removal logic. Don't call this method directly.
150+
* @param snap The snapshot that was removed.
151+
* @param previous The key of the sibling preceding the removed snapshot.
152+
*/
153+
- (void)removeSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(nullable NSString *)previous;
154+
155+
/**
156+
* Called when the Firebase query sends a FIRDataEventTypeChildChanged event. Override this
157+
* to provide custom on change logic. Don't call this method directly.
158+
* @param snap The snapshot whose value was changed.
159+
* @param previous The key of the sibling preceding the changed snapshot.
160+
*/
161+
- (void)changeSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(nullable NSString *)previous;
162+
163+
/**
164+
* Called when the Firebase query sends a FIRDataEventTypeChildMoved event. Override this
165+
* to provide custom move logic. Don't call this method directly.
166+
* @param snap The snapshot that was moved.
167+
* @param previous The key of the sibling preceding the moved snapshot at its new location.
168+
*/
169+
- (void)moveSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(nullable NSString *)previous;
170+
138171
@end
139172

140173
NS_ASSUME_NONNULL_END

FirebaseDatabaseUI/FUIArray.m

Lines changed: 132 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,36 @@ @interface FUIArray ()
2525
/**
2626
* The backing collection that holds all of the array's data.
2727
*/
28-
@property(strong, nonatomic) NSMutableArray<FIRDataSnapshot *> *snapshots;
28+
@property (strong, nonatomic) NSMutableArray<FIRDataSnapshot *> *snapshots;
2929

3030
/**
3131
* A set containing the query observer handles that should be released when
3232
* this array is freed.
3333
*/
34-
@property(strong, nonatomic) NSMutableSet<NSNumber *> *handles;
34+
@property (strong, nonatomic) NSMutableSet<NSNumber *> *handles;
35+
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;
3543

3644
@end
3745

3846
@implementation FUIArray
3947

4048
#pragma mark - Initializer methods
4149

42-
- (instancetype)initWithQuery:(FIRDatabaseQuery *)query delegate:(id<FUIArrayDelegate>)delegate {
50+
- (instancetype)initWithQuery:(FIRDatabaseQuery *)query delegate:(id<FUICollectionDelegate>)delegate {
4351
NSParameterAssert(query != nil);
4452
self = [super init];
4553
if (self) {
4654
self.snapshots = [NSMutableArray array];
4755
self.query = query;
4856
self.handles = [NSMutableSet setWithCapacity:4];
4957
self.delegate = delegate;
50-
[self initListeners];
5158
}
5259
return self;
5360
}
@@ -63,94 +70,94 @@ + (instancetype)arrayWithQuery:(id<FUIDataObservable>)query {
6370
#pragma mark - Memory management methods
6471

6572
- (void)dealloc {
66-
for (NSNumber *handle in _handles) {
67-
[_query removeObserverWithHandle:handle.unsignedIntegerValue];
68-
}
73+
[self invalidate];
6974
}
7075

7176
#pragma mark - Private API methods
7277

73-
- (void)initListeners {
78+
- (void)observeQuery {
79+
if (self.handles.count == 5) { /* don't duplicate observers */ return; }
7480
FIRDatabaseHandle handle;
7581
handle = [self.query observeEventType:FIRDataEventTypeChildAdded
7682
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
77-
NSUInteger index = 0;
78-
if (previousChildKey != nil) {
79-
index = [self indexForKey:previousChildKey] + 1;
80-
}
81-
82-
[self.snapshots insertObject:snapshot atIndex:index];
83-
84-
if ([self.delegate respondsToSelector:@selector(array:didAddObject:atIndex:)]) {
85-
[self.delegate array:self didAddObject:snapshot atIndex:index];
86-
}
83+
[self didUpdate];
84+
[self insertSnapshot:snapshot withPreviousChildKey:previousChildKey];
8785
}
8886
withCancelBlock:^(NSError *error) {
89-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
90-
[self.delegate array:self queryCancelledWithError:error];
91-
}
87+
[self raiseError:error];
9288
}];
9389
[_handles addObject:@(handle)];
9490

9591
handle = [self.query observeEventType:FIRDataEventTypeChildChanged
9692
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
97-
NSUInteger index = [self indexForKey:snapshot.key];
98-
99-
[self.snapshots replaceObjectAtIndex:index withObject:snapshot];
100-
101-
if ([self.delegate respondsToSelector:@selector(array:didChangeObject:atIndex:)]) {
102-
[self.delegate array:self didChangeObject:snapshot atIndex:index];
103-
}
93+
[self didUpdate];
94+
[self changeSnapshot:snapshot withPreviousChildKey:previousChildKey];
10495
}
10596
withCancelBlock:^(NSError *error) {
106-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
107-
[self.delegate array:self queryCancelledWithError:error];
108-
}
97+
[self raiseError:error];
10998
}];
11099
[_handles addObject:@(handle)];
111100

112101
handle = [self.query observeEventType:FIRDataEventTypeChildRemoved
113102
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousSiblingKey) {
114-
NSUInteger index = [self indexForKey:snapshot.key];
115-
116-
[self.snapshots removeObjectAtIndex:index];
117-
118-
if ([self.delegate respondsToSelector:@selector(array:didRemoveObject:atIndex:)]) {
119-
[self.delegate array:self didRemoveObject:snapshot atIndex:index];
120-
}
103+
[self didUpdate];
104+
[self removeSnapshot:snapshot withPreviousChildKey:previousSiblingKey];
121105
}
122106
withCancelBlock:^(NSError *error) {
123-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
124-
[self.delegate array:self queryCancelledWithError:error];
125-
}
107+
[self raiseError:error];
126108
}];
127109
[_handles addObject:@(handle)];
128110

129111
handle = [self.query observeEventType:FIRDataEventTypeChildMoved
130112
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) {
131-
NSUInteger fromIndex = [self indexForKey:snapshot.key];
132-
[self.snapshots removeObjectAtIndex:fromIndex];
133-
134-
NSUInteger toIndex = 0;
135-
if (previousChildKey != nil) {
136-
NSUInteger prevIndex = [self indexForKey:previousChildKey];
137-
if (prevIndex != NSNotFound) {
138-
toIndex = prevIndex + 1;
139-
}
140-
}
141-
142-
[self.snapshots insertObject:snapshot atIndex:toIndex];
143-
144-
if ([self.delegate respondsToSelector:@selector(array:didMoveObject:fromIndex:toIndex:)]) {
145-
[self.delegate array:self didMoveObject:snapshot fromIndex:fromIndex toIndex:toIndex];
146-
}
113+
[self didUpdate];
114+
[self moveSnapshot:snapshot withPreviousChildKey:previousChildKey];
147115
}
148116
withCancelBlock:^(NSError *error) {
149-
if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) {
150-
[self.delegate array:self queryCancelledWithError:error];
151-
}
117+
[self raiseError:error];
152118
}];
153119
[_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];
127+
}];
128+
[_handles addObject:@(handle)];
129+
}
130+
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+
157+
- (void)invalidate {
158+
for (NSNumber *handle in _handles) {
159+
[_query removeObserverWithHandle:handle.unsignedIntegerValue];
160+
}
154161
}
155162

156163
- (NSUInteger)indexForKey:(NSString *)key {
@@ -164,6 +171,69 @@ - (NSUInteger)indexForKey:(NSString *)key {
164171
return NSNotFound;
165172
}
166173

174+
- (void)insertSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(NSString *)previous {
175+
NSUInteger index = 0;
176+
if (previous != nil) {
177+
index = [self indexForKey:previous] + 1;
178+
}
179+
180+
[self.snapshots insertObject:snap atIndex:index];
181+
182+
if ([self.delegate respondsToSelector:@selector(array:didAddObject:atIndex:)]) {
183+
[self.delegate array:self didAddObject:snap atIndex:index];
184+
}
185+
}
186+
187+
- (void)removeSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(NSString *)previous {
188+
NSUInteger index = [self indexForKey:snap.key];
189+
190+
[self.snapshots removeObjectAtIndex:index];
191+
192+
if ([self.delegate respondsToSelector:@selector(array:didRemoveObject:atIndex:)]) {
193+
[self.delegate array:self didRemoveObject:snap atIndex:index];
194+
}
195+
}
196+
197+
- (void)changeSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(NSString *)previous {
198+
NSUInteger index = [self indexForKey:snap.key];
199+
200+
[self.snapshots replaceObjectAtIndex:index withObject:snap];
201+
202+
if ([self.delegate respondsToSelector:@selector(array:didChangeObject:atIndex:)]) {
203+
[self.delegate array:self didChangeObject:snap atIndex:index];
204+
}
205+
}
206+
207+
- (void)moveSnapshot:(FIRDataSnapshot *)snap withPreviousChildKey:(NSString *)previous {
208+
NSUInteger fromIndex = [self indexForKey:snap.key];
209+
[self.snapshots removeObjectAtIndex:fromIndex];
210+
211+
NSUInteger toIndex = 0;
212+
if (previous != nil) {
213+
NSUInteger prevIndex = [self indexForKey:previous];
214+
if (prevIndex != NSNotFound) {
215+
toIndex = prevIndex + 1;
216+
}
217+
}
218+
[self.snapshots insertObject:snap atIndex:toIndex];
219+
220+
if ([self.delegate respondsToSelector:@selector(array:didMoveObject:fromIndex:toIndex:)]) {
221+
[self.delegate array:self didMoveObject:snap fromIndex:fromIndex toIndex:toIndex];
222+
}
223+
}
224+
225+
- (void)removeSnapshotAtIndex:(NSUInteger)index {
226+
[self.snapshots removeObjectAtIndex:index];
227+
}
228+
229+
- (void)insertSnapshot:(FIRDataSnapshot *)snap atIndex:(NSUInteger)index {
230+
[self.snapshots insertObject:snap atIndex:index];
231+
}
232+
233+
- (void)addSnapshot:(FIRDataSnapshot *)snap {
234+
[self.snapshots addObject:snap];
235+
}
236+
167237
#pragma mark - Public API methods
168238

169239
- (NSArray *)items {
@@ -174,7 +244,7 @@ - (NSUInteger)count {
174244
return [self.snapshots count];
175245
}
176246

177-
- (FIRDataSnapshot *)objectAtIndex:(NSUInteger)index {
247+
- (FIRDataSnapshot *)snapshotAtIndex:(NSInteger)index {
178248
return (FIRDataSnapshot *)[self.snapshots objectAtIndex:index];
179249
}
180250

@@ -183,7 +253,7 @@ - (FIRDatabaseReference *)refForIndex:(NSUInteger)index {
183253
}
184254

185255
- (id)objectAtIndexedSubscript:(NSUInteger)index {
186-
return [self objectAtIndex:index];
256+
return [self snapshotAtIndex:index];
187257
}
188258

189259
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index{

0 commit comments

Comments
 (0)