Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#101 Implementation. Add observers with snapshots. #128

Merged
merged 3 commits into from
Oct 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions GeoFire/API/GFQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <FirebaseDatabase/FirebaseDatabase.h>

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -42,6 +43,7 @@ typedef NS_ENUM(NSUInteger, GFEventType) {
};

typedef void (^GFQueryResultBlock) (NSString *key, CLLocation *location);
typedef void (^GFQueryResultSnapshotBlock) (NSString *key, FIRDataSnapshot *snapshot);
typedef void (^GFReadyBlock) (void);

/**
Expand Down Expand Up @@ -78,6 +80,33 @@ typedef void (^GFReadyBlock) (void);

- (FirebaseHandle)observeEventType:(GFEventType)eventType withBlock:(GFQueryResultBlock)block;

/*!
Adds a snapshot observer for an event type.

If you are storing model data and geo data in the same database location,
you may want access to the FIRDataSnapshot as part of geo events.
In this case, use a snapshot observers rather than a key observers.
These snapshot observers have all of the same events as the key observers.

The following event types are supported:

typedef NS_ENUM(NSUInteger, GFEventType) {
GFEventTypeKeyEntered, // A key entered the search area
GFEventTypeKeyExited, // A key exited the search area
GFEventTypeKeyMoved // A key moved within the search area
};

The block is called for each event and snapshot.

Use removeObserverWithFirebaseHandle: to stop receiving callbacks.

@param eventType The event type to receive updates for
@param block The block that is called for updates
@return A handle to remove the observer with
*/

- (FirebaseHandle)observeEventType:(GFEventType)eventType withSnapshotBlock:(GFQueryResultSnapshotBlock)block;

/**
* Adds an observer that is called once all initial GeoFire data has been loaded and the relevant events have
* been fired for this query. Every time the query criteria is updated, this observer will be called after the
Expand Down
108 changes: 104 additions & 4 deletions GeoFire/Implementation/GFQuery.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ @interface GFQueryLocationInfo : NSObject
@property (nonatomic) BOOL isInQuery;
@property (nonatomic, strong) CLLocation *location;
@property (nonatomic, strong) GFGeoHash *geoHash;
@property (nonatomic, strong) FIRDataSnapshot *snapshot;

@end

Expand Down Expand Up @@ -178,6 +179,16 @@ @interface GFQuery ()
@property (nonatomic, strong) NSMutableDictionary *keyEnteredObservers;
@property (nonatomic, strong) NSMutableDictionary *keyExitedObservers;
@property (nonatomic, strong) NSMutableDictionary *keyMovedObservers;

/**
* The group of snapshot observers initialized by calling
* @see GFQuery::observeEventType:withSnapshotBlock:
* Useful when you are storing model data and geo data in the same database location.
*/
@property (nonatomic, strong) NSMutableDictionary *keyEnteredSnapshotObservers;
@property (nonatomic, strong) NSMutableDictionary *keyExitedSnapshotObservers;
@property (nonatomic, strong) NSMutableDictionary *keyMovedSnapshotObservers;

@property (nonatomic, strong) NSMutableDictionary *readyObservers;
@property (nonatomic) NSUInteger currentHandle;

Expand All @@ -204,6 +215,7 @@ - (FIRDatabaseQuery *)firebaseForGeoHashQuery:(GFGeoHashQuery *)query

- (void)updateLocationInfo:(CLLocation *)location
forKey:(NSString *)key
snapshot:(FIRDataSnapshot *)snapshot
{
NSAssert(location != nil, @"Internal Error! Location must not be nil!");
GFQueryLocationInfo *info = self.locationInfos[key];
Expand All @@ -220,6 +232,7 @@ - (void)updateLocationInfo:(CLLocation *)location
info.location = location;
info.isInQuery = [self locationIsInQuery:location];
info.geoHash = [GFGeoHash newWithLocation:location.coordinate];
info.snapshot = snapshot;

if ((isNew || !wasInQuery) && info.isInQuery) {
[self.keyEnteredObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
Expand All @@ -229,6 +242,13 @@ - (void)updateLocationInfo:(CLLocation *)location
block(key, info.location);
});
}];
[self.keyEnteredSnapshotObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
GFQueryResultSnapshotBlock block,
BOOL *stop) {
dispatch_async(self.geoFire.callbackQueue, ^{
block(key, info.snapshot);
});
}];
} else if (!isNew && changedLocation && info.isInQuery) {
[self.keyMovedObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
GFQueryResultBlock block,
Expand All @@ -237,6 +257,13 @@ - (void)updateLocationInfo:(CLLocation *)location
block(key, info.location);
});
}];
[self.keyMovedSnapshotObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
GFQueryResultSnapshotBlock block,
BOOL *stop) {
dispatch_async(self.geoFire.callbackQueue, ^{
block(key, info.snapshot);
});
}];
} else if (wasInQuery && !info.isInQuery) {
[self.keyExitedObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
GFQueryResultBlock block,
Expand All @@ -245,6 +272,13 @@ - (void)updateLocationInfo:(CLLocation *)location
block(key, info.location);
});
}];
[self.keyExitedSnapshotObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
GFQueryResultSnapshotBlock block,
BOOL *stop) {
dispatch_async(self.geoFire.callbackQueue, ^{
block(key, info.snapshot);
});
}];
}
}

Expand All @@ -263,7 +297,7 @@ - (void)childAdded:(FIRDataSnapshot *)snapshot
@synchronized(self) {
CLLocation *location = [GeoFire locationFromValue:snapshot.value];
if (location != nil) {
[self updateLocationInfo:location forKey:snapshot.key];
[self updateLocationInfo:location forKey:snapshot.key snapshot:snapshot];
} else {
// TODO: error?
}
Expand All @@ -275,7 +309,7 @@ - (void)childChanged:(FIRDataSnapshot *)snapshot
@synchronized(self) {
CLLocation *location = [GeoFire locationFromValue:snapshot.value];
if (location != nil) {
[self updateLocationInfo:location forKey:snapshot.key];
[self updateLocationInfo:location forKey:snapshot.key snapshot:snapshot];
} else {
// TODO: error?
}
Expand Down Expand Up @@ -307,6 +341,13 @@ - (void)childRemoved:(FIRDataSnapshot *)snapshot
block(key, location);
});
}];
[self.keyExitedSnapshotObservers enumerateKeysAndObjectsUsingBlock:^(id observerKey,
GFQueryResultSnapshotBlock block,
BOOL *stop) {
dispatch_async(self.geoFire.callbackQueue, ^{
block(key, snapshot);
});
}];
}
}
}
Expand Down Expand Up @@ -391,7 +432,7 @@ - (void)updateQueries
}];
self.queries = newQueries;
[self.locationInfos enumerateKeysAndObjectsUsingBlock:^(id key, GFQueryLocationInfo *info, BOOL *stop) {
[self updateLocationInfo:info.location forKey:key];
[self updateLocationInfo:info.location forKey:key snapshot:info.snapshot];
}];
NSMutableArray *oldLocations = [NSMutableArray array];
[self.locationInfos enumerateKeysAndObjectsUsingBlock:^(id key, GFQueryLocationInfo *info, BOOL *stop) {
Expand Down Expand Up @@ -423,6 +464,9 @@ - (void)reset
self.keyEnteredObservers = [NSMutableDictionary dictionary];
self.keyExitedObservers = [NSMutableDictionary dictionary];
self.keyMovedObservers = [NSMutableDictionary dictionary];
self.keyEnteredSnapshotObservers = [NSMutableDictionary dictionary];
self.keyExitedSnapshotObservers = [NSMutableDictionary dictionary];
self.keyMovedSnapshotObservers = [NSMutableDictionary dictionary];
self.readyObservers = [NSMutableDictionary dictionary];
self.locationInfos = [NSMutableDictionary dictionary];
}
Expand All @@ -441,6 +485,9 @@ - (void)removeObserverWithFirebaseHandle:(FirebaseHandle)firebaseHandle
[self.keyEnteredObservers removeObjectForKey:handle];
[self.keyExitedObservers removeObjectForKey:handle];
[self.keyMovedObservers removeObjectForKey:handle];
[self.keyEnteredSnapshotObservers removeObjectForKey:handle];
[self.keyExitedSnapshotObservers removeObjectForKey:handle];
[self.keyMovedSnapshotObservers removeObjectForKey:handle];
[self.readyObservers removeObjectForKey:handle];
if ([self totalObserverCount] == 0) {
[self reset];
Expand All @@ -450,7 +497,10 @@ - (void)removeObserverWithFirebaseHandle:(FirebaseHandle)firebaseHandle

- (NSUInteger)totalObserverCount
{
return (self.keyEnteredObservers.count +
return (self.keyEnteredSnapshotObservers.count +
self.keyExitedSnapshotObservers.count +
self.keyMovedSnapshotObservers.count +
self.keyEnteredObservers.count +
self.keyExitedObservers.count +
self.keyMovedObservers.count +
self.readyObservers.count);
Expand Down Expand Up @@ -506,6 +556,56 @@ - (FirebaseHandle)observeEventType:(GFEventType)eventType withBlock:(GFQueryResu
}
}

- (FirebaseHandle)observeEventType:(GFEventType)eventType withSnapshotBlock:(GFQueryResultSnapshotBlock)block
{
@synchronized(self) {
if (block == nil) {
[NSException raise:NSInvalidArgumentException format:@"Block is not allowed to be nil!"];
}
FirebaseHandle firebaseHandle = self.currentHandle++;
NSNumber *numberHandle = [NSNumber numberWithUnsignedInteger:firebaseHandle];
switch (eventType) {
case GFEventTypeKeyEntered: {
[self.keyEnteredSnapshotObservers setObject:[block copy]
forKey:numberHandle];
self.currentHandle++;
dispatch_async(self.geoFire.callbackQueue, ^{
@synchronized(self) {
[self.locationInfos enumerateKeysAndObjectsUsingBlock:^(NSString *key,
GFQueryLocationInfo *info,
BOOL *stop) {
if (info.isInQuery) {
block(key, info.snapshot);
}
}];
};
});
break;
}
case GFEventTypeKeyExited: {
[self.keyExitedSnapshotObservers setObject:[block copy]
forKey:numberHandle];
self.currentHandle++;
break;
}
case GFEventTypeKeyMoved: {
[self.keyMovedSnapshotObservers setObject:[block copy]
forKey:numberHandle];
self.currentHandle++;
break;
}
default: {
[NSException raise:NSInvalidArgumentException format:@"Event type was not a GFEventType!"];
break;
}
}
if (self.queries == nil) {
[self updateQueries];
}
return firebaseHandle;
}
}

- (FirebaseHandle)observeReadyWithBlock:(GFReadyBlock)block
{
@synchronized(self) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1030"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FBBC43CB196AEFE500C0CAA5"
BuildableName = "SFVehicles.app"
BlueprintName = "SFVehicles"
ReferencedContainer = "container:SFVehicles.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FBBC43CB196AEFE500C0CAA5"
BuildableName = "SFVehicles.app"
BlueprintName = "SFVehicles"
ReferencedContainer = "container:SFVehicles.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FBBC43CB196AEFE500C0CAA5"
BuildableName = "SFVehicles.app"
BlueprintName = "SFVehicles"
ReferencedContainer = "container:SFVehicles.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FBBC43CB196AEFE500C0CAA5"
BuildableName = "SFVehicles.app"
BlueprintName = "SFVehicles"
ReferencedContainer = "container:SFVehicles.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>