Currently my location code is all part of a singleton. I am curious if this is what is causing my problems. I am using the location singleton as part of both geofence regions and iBeacons. I have multiple iBeacons in the location I am testing and it seems that sometimes a enter of a beacon region interrupts the exit of a previous region. I am curious if this is happening because this is a singleton and if I should change this to a standard class that the app delegate initializes to start the monitoring and the app will handle the calls backs for each call back that occurs. I think that since it is a singleton the second call back that occurs is stopping the first call back if it hasn't completed.
You can see from the log below that I exited the basement and it started a timed event to make sure I truly exited the basement (software filter to get rid of the rapid exit/enter that occurs with iBeacons). Then I walked into my living room which fired another region event but we never get the message didExitRegion: Basement logged out.
2014-04-25 15:35:22.757 [2761:707] Just Requested Background Time
2014-04-25 15:35:22.761 [2761:707] Basement
2014-04-25 15:35:22.767 [2761:707] Start Timed Event To See If Its A False Exit
2014-04-25 15:35:22.777 [2761:707] Just Requested Background Time
2014-04-25 15:35:22.778 [2761:707] Basement
2014-04-25 15:35:22.780 [2761:707] Start Timed Event To See If Its A False Exit
2014-04-25 15:35:23.751 [2761:707] Just Requested Background Time
2014-04-25 15:35:23.753 [2761:707] didEnterRegion: Living Room
2014-04-25 15:35:23.772 [2761:707] Just Requested Background Time
2014-04-25 15:35:23.774 [2761:707] didEnterRegion: Living Room
2014-04-25 15:35:24.270 [2761:707] Job ID Value
2014-04-25 15:35:24.272 [2761:707] 3208
New log after changes made to nstimer:
2014-04-25 17:13:06.873 [3243:707] Just Requested Background Time
2014-04-25 17:13:06.875 [3243:707] Kitchen
2014-04-25 17:13:06.877 [3243:707] Start Timed Event To See If Its A False Exit
2014-04-25 17:13:06.884 [3243:707] Just Requested Background Time
2014-04-25 17:13:06.885 [3243:707] Kitchen
2014-04-25 17:13:06.886 [3243:707] Start Timed Event To See If Its A False Exit
2014-04-25 17:13:09.146 [3243:707] didExitRegion Basement
2014-04-25 17:13:09.149 [3243:707] Cancelled Exit Of Region: Basement
2014-04-25 17:13:09.881 [3243:707] Job ID Value
2014-04-25 17:13:09.883 [3243:707] 4040
2014-04-25 17:13:09.884 [3243:707] Connection Successful
2014-04-25 17:13:10.878 [3243:707] Cancelled Exit Of Region: Kitchen
2014-04-25 17:13:10.889 [3243:707] Cancelled Exit Of Region: Kitchen
Code Updated:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
currentRegion = region;
GeofenceObject *gObject = [[GeofenceObject alloc] init];
gObject = [self getObjectForGeofence:region];
//stop multiple notification deliveries
if (notificationDelivered && ![gObject getIsBeacon])
{
notificationDelivered = NO;
return;
}
BOOL alwaysNotify = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysNotifyLocation"];
if (![self checkWifi] && ![self checkWWLAN] && ![gObject getIsBeacon])
{
[self notifyNoNetwork:gObject forState:[NSNumber numberWithInt:1]];
return;
}
if ((alwaysNotify && ![gObject getIsBeacon]))
{
[self notifyAlways:gObject forState:[NSNumber numberWithInt:1]];
return;
}
BOOL isInBackground = NO;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
isInBackground = YES;
}
NSTimer *timer = (NSTimer *)[self.timers objectForKey:[gObject getGeofenceName]];
if (timer != nil)
{
[timer invalidate];
[self.timers removeObjectForKey:[gObject getGeofenceName]];
}
if (isInBackground && [gObject getIsBeacon])
{
[self beginBackgroundTask];
NSLog(@"%@", [@"didEnterRegion: " stringByAppendingString:[gObject getGeofenceName]]);
[self didEnterRegion:region forObject:gObject];
return;
}
if (isInBackground)
{
[self beginBackgroundTask];
NSLog(@"%@",[@"didEnterRegion " stringByAppendingString:[gObject getGeofenceName]]);
[self didEnterRegion:region forObject:gObject];
return;
}
if ([gObject getIsBeacon])
{
NSLog(@"%@", [@"didEnterRegion: " stringByAppendingString:[gObject getGeofenceName]]);
[self didEnterRegion:region forObject:gObject];
}
NSLog(@"%@",[@"didEnterRegion " stringByAppendingString:[gObject getGeofenceName]]);
[self didEnterRegion:region forObject:gObject];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
currentRegion = region;
GeofenceObject *gObject = [[GeofenceObject alloc] init];
gObject = [self getObjectForGeofence:region];
//stop multiple notification deliveries
if (notificationDelivered && ![gObject getIsBeacon])
{
notificationDelivered = NO;
return;
}
BOOL alwaysNotify = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysNotifyLocation"];
if (![self checkWifi] && ![self checkWWLAN] && ![gObject getIsBeacon])
{
[self notifyNoNetwork:gObject forState:[NSNumber numberWithInt:0]];
return;
}
if ((alwaysNotify && ![gObject getIsBeacon]))
{
[self notifyAlways:gObject forState:[NSNumber numberWithInt:0]];
return;
}
BOOL isInBackground = NO;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
isInBackground = YES;
}
if (isInBackground && [gObject getIsBeacon])
{
[self beginBackgroundTask];
[gObject setJustExitedRegion:YES];
[self replaceObjectWithUpdate:gObject];
NSDictionary *info = [NSDictionary dictionaryWithObject:region forKey:@"region"];
NSLog(@"%@", [gObject getGeofenceName]);
NSLog(@"%@", @"Start Timed Event To See If Its A False Exit");
[self.timers setObject:[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO] forKey:[gObject getGeofenceName]];
return;
}
if (isInBackground)
{
[self beginBackgroundTask];
NSLog(@"%@",[@"didExitRegion " stringByAppendingString:[gObject getGeofenceName]]);
[self didExitRegion:region forObject:gObject];
return;
}
if ([gObject getIsBeacon])
{
[gObject setJustExitedRegion:YES];
[self replaceObjectWithUpdate:gObject];
NSDictionary *info = [NSDictionary dictionaryWithObject:region forKey:@"region"];
NSLog(@"%@", [gObject getGeofenceName]);
NSLog(@"%@", @"Start Timed Event To See If Its A False Exit");
[self.timers setObject:[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO] forKey:[gObject getGeofenceName]];
return;
}
NSLog(@"%@",[@"didExitRegion " stringByAppendingString:[gObject getGeofenceName]]);
[self didExitRegion:region forObject:gObject];
}
- (void)beginBackgroundTask
{
NSLog(@"%@", @"Just Requested Background Time");
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"%@", @"Just Cancelled Background Time");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
- (void)checkBackOnExit:(NSTimer *)timer
{
NSDictionary *info = [timer userInfo];
CLRegion *region = [info objectForKey:@"region"];
GeofenceObject *gObject = [[GeofenceObject alloc] init];
gObject = [self getObjectForGeofence:region];
if ([gObject getJustExitedRegion])
{
[self didExitRegion:region forObject:gObject];
NSLog(@"%@",[@"didExitRegion " stringByAppendingString:[gObject getGeofenceName]]);
[gObject setJustExitedRegion:NO];
[self replaceObjectWithUpdate:gObject];
[timer invalidate];
[self.timers removeObjectForKey:[gObject getGeofenceName]];
info = nil;
}else
{
[timer invalidate];
[self.timers removeObjectForKey:[gObject getGeofenceName]];
NSLog(@"%@", [@"Cancelled Exit Of Region: " stringByAppendingString:[gObject getGeofenceName]]);
info = nil;
return;
}
}
Your problem is this line -
[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO];
You don't store the new NSTimer
that is returned from scheduledTimerWithTimeInterval
anywhere so as soon as the method exits it will be deallocated. You need to add the NSTimer to a property. As you can possibly have multiple timers running concurrently I would suggest an NSMutableDictionary
.
@property (retain,nonatomic) NSMutableDictionary *timers;
Initialise this wherever appropriate (such as init
)
self.timers=[[NSMutableDictionary alloc]init];
then in didExitRegion:
[self.timers addObject:[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:@selector(checkBackOnExit:) userInfo:info repeats:NO] forKey:gObject.geoFenceName];
In didEnterRegion:
NSTimer *timer=(NSTimer *)[self.timers objectForKey:gObject.geoFenceName]
if (timer != nil) {
[timer invalidate];
[self.timers removeObjectForKey:gObject.geoFenceName];
}
And also in your checkBackOnExit:
method
[timer invalidate];
[self.timers removeObjectForKey:gObject.geoFenceName];